ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 wasm-bindgen + web-sys 构建天气查询 Web 应用:从 OpenWeatherMap API 到 DOM 渲染的完整实践

用 wasm-bindgen + web-sys 构建天气查询 Web 应用:从 OpenWeatherMap API 到 DOM 渲染的完整实践 开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载导读本文基于 wasm-bindgen 仓库中的weather_report示例examples/weather_report完整讲解如何用 Rust 编写 Wasm 模块在浏览器中调用 OpenWeatherMap 天气 API、解析 JSON 响应、动态构建 DOM 表格与交互式地图并借助spawn_local处理异步任务。读完本文你将掌握#[wasm_bindgen(start)]入口、#[wasm_bindgen(module ...)]导入外部 JS 函数、web-sys 的 DOM API 编程以及 wasm-pack webpack 的整套构建与本地运行流程。示例概览一个用 Rust 驱动浏览器 UI 的天气应用weather_report是 wasm-bindgen 官方仓库中一个重型前端示例与 hello_world、add 等最小示例不同它把 Rust 当作完整的 UI 层来使用用户输入城市名并点击 Search 按钮Rust 通过reqwest向 OpenWeatherMap API 发起 HTTP 请求用jsoncrate 解析返回的 JSON用web_sys提供的Document、Element、HtmlInputElement等类型在页面上动态创建表格展示温度、气压、湿度、日出日落、地理坐标通过导入的外部 JS 函数initialize(lat, lon)在页面上渲染 Google 地图标记。官方文档 guide/src/examples/weather_report.md 对该示例的定位是向 OpenWeather API 发起 HTTP 请求、解析 JSON 并据此渲染 UI同时演示spawn_local在异步任务中的用法。示例由以下文件组成文件职责src/lib.rsRust 业务逻辑DOM 构建、事件绑定、异步请求与渲染Cargo.toml依赖声明wasm-bindgen、web-sys、reqwest、chrono 等index.html页面骨架与内联样式引用 Google Maps APIindex.jswebpack 入口动态import(./pkg)加载 Wasmutil.js被 Rust 通过module导入的 JS 函数Google Maps 渲染webpack.config.jswasm-pack 插件与开发服务器配置package.jsonnpm 脚本与前端构建依赖环境准备与依赖清单Cargo 依赖解读examples/weather_report/Cargo.toml 声明了本次示例的全部 Rust 依赖[package] authors [Ayush ayushmishra2005gmail.com] edition 2021 name rust-webassembly-weather-reports publish false version 0.0.0 [lib] crate-type [cdylib] [dependencies] chrono 0.4.11 gloo 0.11 json 0.12 reqwest 0.13 wasm-bindgen { path ../../ } wasm-bindgen-futures { path ../../crates/futures } [dependencies.web-sys] features [Document, Element, HtmlElement, Window] path ../../crates/web-sys需要重点说明的几点crate-type [cdylib]是 Wasm 项目的标准配置它把 crate 编译成 C 动态库形式的.wasm产物供 wasm-pack 生成 JS 绑定。wasm-bindgen与wasm-bindgen-futures使用仓库相对路径path ../../指向仓库根path ../../crates/futures指向 futures crate说明示例直接基于仓库源码开发wasm-bindgen-futures正是提供spawn_local的 crate其实现已在 crates/futures/src/lib.rs 中注明thin shim re-exporting fromjs_sys::futures即spawn_local实际定义于 js-sys 的 futures 模块。web-sys采用按 feature 裁剪的方式这里只开启了Document、Element、HtmlElement、Window四个 feature。web-sys 的 API 面极其庞大crates/web-sys/src 下有上千个绑定文件按需开启 feature 是控制编译体积的标准做法。本示例运行时还会用到HtmlInputElement的dyn_into转换但该转换本身来自 wasm-bindgen 的JsCasttrait无需额外 feature。前端构建依赖examples/weather_report/package.json 提供了两个 npm 脚本{ scripts: { build: webpack, serve: webpack serve }, devDependencies: { wasm-tool/wasm-pack-plugin: catalog:, html-webpack-plugin: catalog:, copy-webpack-plugin: catalog:, webpack: catalog:, webpack-cli: catalog:, webpack-dev-server: catalog: } }其中wasm-tool/wasm-pack-plugin负责在 webpack 构建时自动调用 wasm-pack 编译 Rust cratehtml-webpack-plugin以 index.html 为模板生成页面copy-webpack-plugin把assets目录bootstrap 样式与图片原样复制到产物目录。构建与本地运行在 examples/weather_report 目录下依次执行以下命令即可构建并启动示例$ npm install $ npm run build $ npm start然后打开浏览器访问http://localhost:8080即可运行示例。关于命令的细节说明npm install会安装上述 devDependencies依赖版本由仓库根部的 pnpm workspace catalog 统一解析。npm run build触发 webpack.config.js 中的WasmPackPlugin它读取crateDirectory: __dirname自动对当前目录的 Cargo crate 执行wasm-pack build将 Rust 编译为.wasm并生成pkg/下的 JS 胶水代码。页面输出到../dist/weather_report入口与模板均来自./index.js和./index.html。构建模式为development并开启了experiments: { syncWebAssembly: true }允许 webpack 同步加载 WebAssembly 模块。⚠️ 运行前提OpenWeatherMap 与 Google Maps 均为外部服务。请先在 src/lib.rs 的get_response()中填入你自己的 OpenWeather API KeyappidapiKey占位符页面也需要可访问 Google Maps JS API见 index.html 的script引入。本示例内部逻辑与 GitHub Pages 上的预编译演示均依赖这些外部服务。程序入口#[wasm_bindgen(start)]与大多数示例一样weather_report 用#[wasm_bindgen(start)]标记一个run()函数作为 Wasm 模块加载后的自动执行入口src/lib.rs#[wasm_bindgen(start)] fn run() - Result(), JsValue { // ... }run()返回Result(), JsValueWasm 初始化过程中的任何 DOM 操作错误如取不到window、document、body都会以JsValue错误形式向上传播。入口内部的核心流程为通过web_sys::window()获取全局window再逐级取得document与body调用辅助函数create_div、create_input_box、create_submit_box创建搜索区与详情区元素用document.create_element手工拼装一个六行的天气信息表格Pressure、Humidity、Sunrise、Sunset、Geo coords为 Search 按钮挂载EventListener在点击回调中发起异步请求并渲染结果最后调用on_click.forget()防止闭包被回收见下文。用 web-sys 动态构建 DOM元素工厂函数create_div、create_input_box、create_submit_box三个辅助函数展示了 web-sys 最基础的 DOM 编程范式src/lib.rsfn create_div(document: Document, id: str, class: str) - Element { let div document.create_element(div).unwrap(); div.set_id(id); div.set_class_name(class); div }document.create_element(div)返回ResultElement, JsValue随后通过set_id、set_class_name、set_attribute等方法设置属性。输入框与按钮也是用同样的方式创建的例如create_submit_box依次设置typebutton、valueSearch、namesubmit、idsubmit与 Bootstrap 样式类src/lib.rs。表格的逐节点组装run()中有一段相当啰嗦但结构清晰的表格构建代码它展示了append_child的嵌套组装方式src/lib.rslet table_div document.create_element(table)?; table_div.set_class_name(ReportStyles-table table-bordered table-striped); let tbody_div document.create_element(tbody)?; let ftr_div document.create_element(tr)?; let ftd_div document.create_element(td)?; ftd_div.set_class_name( ReportStyles-firstTd); let img_div document.create_element(div)?; img_div.set_id(temp); let std_div document.create_element(td)?; std_div.set_class_name( ReportStyles-secondTd); let weather_div document.create_element(div)?; weather_div.set_id(weather); ftr_div.append_child(ftd_div)?; ftd_div.append_child(img_div)?; ftr_div.append_child(std_div)?; std_div.append_child(weather_div)?; // ... 其余行Pressure/Humidity/Sunrise/Sunset/Geo coords同理这种先建元素、设属性、再挂子节点的写法本质上是把 HTML 的层级结构翻译成一系列 Rust 方法调用。每个带id的叶子节点temp、weather、pressure、humidity、sunrise、sunset、geocoords、map_canvas都是后续异步渲染时用set_inner_html填充数据的插槽。页面最终布局由 index.html 中内联的ReportStyles-*样式控制主容器.ReportStyles-mainContainer默认display: none只有拿到 API 响应后才由 Rust 代码通过set_attribute(style, display: block)显示出来src/lib.rs。从 Rust 导入外部 JS#[wasm_bindgen(module ...)]示例中 Google 地图的渲染不是用 web-sys 实现的而是通过 wasm-bindgen 的模块导入机制调用手写 JS#[wasm_bindgen(module /util.js)] extern C { fn initialize(lat: f64, lon: f64); }module /util.js让编译器把 util.js 当作 ES 模块导入并在 Wasm 侧暴露initialize(lat, lon)函数。util.js 内部使用全局google.mapsAPI 创建地图与标记export function initialize(lat, lon) { var myLatlng new google.maps.LatLng(lat, lon); var myOptions { zoom: 3, center: myLatlng, mapTypeId: google.maps.MapTypeId.ROADMAP } var map new google.maps.Map(document.getElementById(map_canvas), myOptions); var marker new google.maps.Marker({ position: myLatlng, title:Hello World! }); marker.setMap(map); }initialize接收 Rust 传入的经纬度把地图中心对准目标城市并打上标记。该函数在 Rust 侧是fire-and-forget式调用不返回任何值异步请求解析完成后直接以initialize(lat, lon)触发src/lib.rs。事件绑定与闭包生命周期EventListener 与 forgetSearch 按钮的事件处理采用 gloo 的EventListenerlet on_click EventListener::new(submit_box, click, move |_event| { // 读取输入框值发起异步请求渲染结果 }); on_click.forget();闭包内部先通过document.get_element_by_id(name)找到输入框再dyn_into::HtmlInputElement()把它向下转型为具体类型并读取.value()src/lib.rs。dyn_into是 wasm-bindgen 的JsCasttrait 提供的方法web_sys::HtmlInputElement在这里正是通过它获得对应 Cargo.toml 中开启的HtmlElementfeature 家族的 DOM 类型体系。闭包生命周期是本节的关键陷阱。代码末尾的注释明确说明了原因src/lib.rs// When a Closure is dropped it will invalidate the associated JS closure. // Here we want JS callback to be alive for the entire duration of the program. // So we used forget leak this instance of Closure. // It should be used sparingly to ensure the memory leak doesnt affect the program too much.EventListener/Closure一旦被 Rust 侧 drop对应的 JS 回调就会失效为了让回调在整个程序生命周期内存活必须调用forget()主动泄漏。注释同时提醒这种泄漏应当谨慎使用以控制其对内存的影响。这是 Rust↔JS 事件绑定中极易踩坑、也最需要理解的设计点。异步 HTTP 请求与 spawn_local请求函数async fn get_response(location: str) - JsonValue { let url1 http://api.openweathermap.org/data/2.5/weather?q; let url2 appidapiKey; let url [url1, location, url2].concat(); let resp reqwest::get(url).await.unwrap().text().await.unwrap(); json::parse(resp).unwrap() }get_response用reqwest::get发起请求并等待文本响应再用jsoncrate 的json::parse得到JsonValue。运行前必须把appidapiKey替换为真实的 OpenWeather API Key——官方文档 guide/src/examples/weather_report.md 对此有明确提醒Please add your api key inget_response()before running this application.spawn_local在浏览器事件循环里跑 FutureRust 的async函数不能直接在点击回调里.await——那会阻塞事件循环。示例的正确姿势是把 Future 交给wasm_bindgen_futures::spawn_local调度let response get_response(input_value); spawn_local(async move { let parsed response.await; // ... 提取字段并渲染 DOM });spawn_local会把 Future 挂在当前 JS 上下文这里是浏览器的事件循环上异步推进直到await完成。正如官方文档所述本示例正是为了展示spawn_local在异步任务处理中的用法。它由wasm-bindgen-futurescrate 提供而该 crate 在 crates/futures/src/lib.rs 中被描述为thin shim——实现已迁移到 js-sys 的 futures 模块js_sys::futures::spawn_local这里只是为兼容旧 API 而原样 re-export。从 JsonValue 提取数据并渲染响应解析完成后代码从JsonValue中逐字段取值src/lib.rslet lon parsed[coord][lon].to_owned().as_f64().unwrap(); let lat parsed[coord][lat].to_owned().as_f64().unwrap(); initialize(lat, lon); let city_name: str parsed[name].to_owned().to_string(); let country_name: str parsed[sys][country].to_owned().to_string(); let place [city_name, ,, country_name].concat(); let icon parsed[weather][0][icon].to_owned().to_string(); let src [ img srchttp://openweathermap.org/img/w/, icon, .png, , ] .concat(); let temp (parsed[main][temp].to_owned().as_f64().unwrap() - 273.15) as i64;值得注意的实现细节OpenWeatherMap 返回的温度单位是开尔文示例中显式做了- 273.15换算成摄氏度再as i64取整。天气图标直接拼接 OpenWeatherMap 的图片 URL 生成img标签。城市名与国别拼成City,COUNTRY格式显示在标题处。随后用先前预留的带id元素完成填充temp_d.set_attribute(style, display: block)...; city.set_inner_html(place); image.set_inner_html(content); weather.set_inner_html(parsed[weather][0][main].to_owned().to_string()); pressure.set_inner_html(([p, hpa].concat())); humidity.set_inner_html(([h, %].concat())); sunrise.set_inner_html(get_time(sun_r)); sunset.set_inner_html(get_time(sun_s)); geo.set_inner_html(([[, lon.to_string(), ,, lat.to_string(), ]].concat()));set_inner_html是 web-sys 提供的便捷方法直接把 HTML 字符串写入元素内部省去了逐节点构建的繁琐。时间戳格式化chrono 的用法OpenWeatherMap 的sunrise/sunset字段是 Unix 秒级时间戳示例用 chrono 把它格式化成HH:MM:SSsrc/lib.rsfn get_time(millis: u64) - String { let d UNIX_EPOCH Duration::from_secs(millis); // Create DateTime from SystemTime let datetime DateTime::Utc::from(d); // Formats the combined date and time with the specified format string. datetime.format(%H:%M:%S).to_string() }先把UNIX_EPOCH加上秒数得到SystemTime再转成DateTimeUtc最后用%H:%M:%S输出 UTC 时刻——表格中对应的列标题正是Sunrise[UTC]与Sunset[UTC]。运行效果上图是 assets/images/weather.png 记录的示例运行截图页面顶部为标题栏WEATHER REPORT - Get the mood of your city on one click中间是城市输入搜索框查询Delhi,IN后左侧表格展示了天气状况霾、温度、气压、湿度、日出日落时间与地理坐标右侧则是由 util.js 通过 Google Maps 渲染的城市地图。整个过程搜索 → 请求 → 渲染 → 地图定位全部由 Rust 编译出的 Wasm 模块驱动JS 仅承担入口加载与地图渲染两处薄层。常见问题与调试要点页面无数据显示首先检查是否已在get_response()中填入真实 API KeyappidapiKey占位符不会返回有效数据其次确认npm install、npm run build均成功、pkg/已生成。地图不显示确认浏览器可访问 Google Maps JS APIindex.html 通过script直接加载且initialize被正确导入——对应 src/lib.rs 的module /util.js声明。点击按钮后回调不触发检查on_click.forget()是否被调用一旦忘记forgetEventListener被 drop 后 JS 侧闭包即失效见 src/lib.rs 的注释说明。API 域名限制OpenWeatherMap 使用http://非 HTTPS的请求 URL部分现代浏览器可能以混合内容策略拦截需要按浏览器提示调整运行环境。web-sys 类型转换读取输入框值必须dyn_into::HtmlInputElement()后调用.value()因为get_element_by_id返回的是宽泛的Element类型src/lib.rs。延伸阅读仓库根 README.md 与 examples/README.md 概述了 wasm-bindgen 及其示例集的定位官方文档对应章节 guide/src/examples/weather_report.md 是本文的源头文档想深入异步机制可阅读 crates/futures/src/lib.rsspawn_local的 re-export 说明与 crates/js-sys/src/lib.rs 中的 futures 模块实现其他由#[wasm_bindgen(start)]驱动、配合 webpack 的示例如 hello_world、console_log可作为对照理解从最小骨架到完整 UI 的演进路径。赞分享开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载相关推荐web-sys 实战用 wasm-bindgen 构建调用 OpenWeather API 的天气报告应用web sys 实战用 wasm bindgen 构建调用 OpenWeather API 的天气报告应用 本指南基于 wasm bindgen 仓库中的 w开发工具使用 AWS SDK for C 操作 IAM从单个 Action 到完整用户-角色实战场景使用 AWS SDK for C 操作 IAM从单个 Action 到完整用户 角色实战场景 导读 本篇文章聚焦于当前仓库 cpp/example_cod开发工具wasm-bindgen web-sys 操作 DOM从零构建并运行 Hello from Rust! 示例wasm bindgen web sys 操作 DOM从零构建并运行 Hello from Rust! 示例 本文以 wasm bindgen 仓库中开发工具上一篇在 Laravel 中安装 daisyUI 5Tailwind CSS 组件库的完整接入指南下一篇Apache Gluten函数支持详解如何扩展原生执行引擎的功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表