
开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载导读本文基于 wasm-bindgen 官方示例 examples/wasm-in-web-worker完整拆解如何用 Rust 编译出的 Wasm 模块在Web Worker 后台线程中执行计算任务并通过postMessage/onmessage事件机制与主线程双向通信。读完本文你将掌握为什么跨浏览器场景必须使用--target no-modules构建、如何在 Rust 侧用web_sys::Worker创建 Worker、如何用wasm_bindgen::Closure为 DOM 事件与 Worker 消息绑定回调以及如何在 Worker 中用带状态的结构体NumberEval响应消息并返回结果。示例整体架构与运行效果该示例实现的是一个简单但完整的主线程 / Worker 交互演示用户在页面输入框#inputNumber中输入数字主线程中的 Wasm 代码监听到oninput事件后把数字postMessage给 WorkerWorker 中的 Wasm 代码调用NumberEval::is_even判断奇偶再把布尔结果postMessage回主线程主线程最终把even或odd写入#resultField元素。关键点在于主线程与 Worker 各自加载同一份 Wasm 二进制但使用不同的 Rust 导出线程使用的导出职责主线程startup()创建 Worker、绑定输入框事件、接收并渲染结果WorkerNumberEval结构体持有状态、执行奇偶判断、返回结果整个示例由 4 个核心文件组成全部位于 examples/wasm-in-web-workersrc/lib.rsRust 侧全部逻辑Worker 创建、回调绑定、结构体实现index.html页面骨架加载两个非模块脚本index.js主线程侧初始化 Wasm 并调用startup()worker.jsWorker 侧初始化 Wasm 并监听消息。构建方式与跨浏览器兼容性为什么必须用--target no-modules原文档明确指出在编写该示例时只有 Chrome 支持在 Web Worker 中使用 ES modules例如 Firefox 不支持。为了保证跨浏览器兼容整个示例完全不依赖 ES modules作为输出目标因此必须使用 wasm-pack 的--target no-modules构建。构建命令记录在示例根目录的 package.json 中{ scripts: { build: wasm-pack build --target no-modules --out-dir ../dist/wasm-in-web-worker, postbuild: node ../cp-dist.mjs index.html index.js style.css worker.js } }wasm-pack build --target no-modules生成全局变量风格的 JS glue 代码wasm_in_web_worker.js与 Wasm 二进制wasm_in_web_worker_bg.wasm不产生import/export语句因此可以用普通script标签和importScripts加载--out-dir ../dist/wasm-in-web-worker输出到 dist 目录postbuild调用 cp-dist.mjs把index.html、index.js、style.css、worker.js一并复制到 dist保证页面可以直接访问。这也解释了 index.html 中脚本标签的特殊写法!-- Make wasm_bindgen available for index.js -- script src./wasm_in_web_worker.js/script !-- Note that there is no typemodule in the script tag -- script src./index.js/script第一行把生成的 glue 文件作为全局脚本加载使wasm_bindgen全局对象可用第二行刻意不加typemodule因为生成的是非模块脚本。注释原文特意强调注意此 script 标签没有typemodule这正是no-modules目标的核心特征。wasm-pack各 target 的适用场景补充说明--target no-modules只是 wasm-pack 支持的 target 之一。从构建产物与加载方式看可以这样理解各 target 的取舍--target bundler面向 webpack 等打包器产物含 ESMimport/export--target no-modules面向浏览器全局脚本产物直接挂到window全局对象适合用script与importScripts加载--target web面向原生浏览器 ESM 导入产物含import.meta.url等 ESM 语法无法在 Worker 的importScripts环境中直接使用。本示例选择no-modules就是为了让同一份产物既能被主线程的script标签加载又能被 Worker 的importScripts加载实现跨浏览器兼容。依赖配置Cargo.toml 的 feature 清单examples/wasm-in-web-worker/Cargo.toml 中启用的 web-sys feature 恰好对应示例需要的全部 DOM / Worker API[package] authors [The wasm-bindgen Developers] edition 2021 name wasm-in-web-worker publish false version 0.0.0 [lib] crate-type [cdylib] [dependencies] console_error_panic_hook { version 0.1.6, optional true } wasm-bindgen { path ../../ } [dependencies.web-sys] features [ console, Document, HtmlElement, HtmlInputElement, MessageEvent, Window, Worker, ] path ../../crates/web-sys各 feature 与代码的对应关系如下web-sys feature对应 API在示例中的用途consoleweb_sys::console打印调试日志console::log_1/console::log_2Documentweb_sys::Document获取#inputNumber、#resultField等 DOM 元素HtmlElementweb_sys::HtmlElement对结果元素调用set_inner_textHtmlInputElementweb_sys::HtmlInputElement读取输入框value()、绑定oninputMessageEventweb_sys::MessageEventWorker 消息回调的参数类型Windowweb_sys::Window通过window()获取 DocumentWorkerweb_sys::Worker在主线程创建并持有 Worker两点值得注意[lib] crate-type [cdylib]是 wasm-bindgen 项目的标配确保 Rust 代码被编译成可被 JS 调用的 Wasm 动态库示例直接以path依赖仓库内的 crates/web-sys 与根目录的 wasm-bindgenpath ../../而不是从 crates.io 拉取说明这是仓库内联示例的常规做法在真实项目中改为wasm-bindgen 0.2、web-sys 0.3等版本号即可。Rust 侧实现src/lib.rs 逐段解析src/lib.rs 是本示例的灵魂它同时承担了主线程入口与Worker 状态对象两个角色。带状态的NumberEval结构体/// A number evaluation struct /// /// This struct will be the main object which responds to messages passed to the /// worker. It stores the last number which it was passed to have a state. The /// statefulness is not required in this example but should show how /// larger, more complex scenarios with statefulness can be set up. #[wasm_bindgen] pub struct NumberEval { number: i32, } #[wasm_bindgen] impl NumberEval { /// Create new instance. pub fn new() - NumberEval { NumberEval { number: 0 } } /// Check if a number is even and store it as last processed number. pub fn is_even(mut self, number: i32) - bool { self.number number; self.number % 2 0 } /// Get last number that was checked - this method is added to work with /// statefulness. pub fn get_last_number(self) - i32 { self.number } }要点#[wasm_bindgen]同时标注在pub struct与impl块上之后该结构体与new、is_even、get_last_number就会被导出到生成的 JS glue 中供worker.js以NumberEval.new()、num_eval.is_even(...)调用注释明确说明状态保持statefulness在本示例中并非必需其目的是示范更复杂、需要跨消息保持状态的场景该如何搭建——把number字段存在结构体里两次is_even调用之间状态得以保留is_even接收mut self因为要写入self.numberget_last_number只读用self。主线程入口startup/// Run entry point for the main thread. #[wasm_bindgen] pub fn startup() { // Here, we create our worker. In a larger app, multiple callbacks should be // able to interact with the code in the worker. Therefore, we wrap it in // RcRefCell following the interior mutability pattern. Here, it would // not be needed but we include the wrapping anyway as example. let worker_handle Rc::new(RefCell::new(Worker::new(./worker.js).unwrap())); console::log_1(Created a new worker from within Wasm.into()); // Pass the worker to the function which sets up the oninput callback. setup_input_oninput_callback(worker_handle); }Worker::new(./worker.js)用web_sys从Rust 代码内部创建 Worker——这正是在 Wasm 中创建 Worker的体现Worker 句柄被包进RcRefCell。注释解释了这样做的原因在更大的应用中可能有多个回调需要共享同一个 WorkerRc提供共享所有权、RefCell提供运行时可变借用内部可变性模式。本示例虽然只有一个回调在使用但为了演示这一惯用法仍然包了一层console::log_1(Created a new worker from within Wasm.into())依赖consolefeature在控制台输出一条日志用于确认 Worker 已创建。绑定oninput回调setup_input_oninput_callbackfn setup_input_oninput_callback(worker: RcRefCellweb_sys::Worker) { let document web_sys::window().unwrap().document().unwrap(); // If our onmessage callback should stay valid after exiting from the // oninput closure scope, we need to either forget it (so it is not // destroyed) or store it somewhere. ... #[allow(unused_assignments)] let mut persistent_callback_handle get_on_msg_callback(); #[allow(unused_assignments)] let callback Closure::new(move || { console::log_1(oninput callback triggered.into()); let document web_sys::window().unwrap().document().unwrap(); let input_field document .get_element_by_id(inputNumber) .expect(#inputNumber should exist); let input_field input_field .dyn_ref::HtmlInputElement() .expect(#inputNumber should be a HtmlInputElement); // If the value in the field can be parsed to a i32, send it to the // worker. Otherwise clear the result field. match input_field.value().parse::i32() { Ok(number) { // Access worker behind shared handle, following the interior // mutability pattern. let worker_handle *worker.borrow(); let _ worker_handle.post_message(number.into()); persistent_callback_handle get_on_msg_callback(); // Since the worker returns the message asynchronously, we // attach a callback to be triggered when the worker returns. worker_handle .set_onmessage(Some(persistent_callback_handle.as_ref().unchecked_ref())); } Err(_) { document .get_element_by_id(resultField) .expect(#resultField should exist) .dyn_ref::HtmlElement() .expect(#resultField should be a HtmlInputElement) .set_inner_text(); } } }); // Attach the closure as oninput callback to the input field. document .get_element_by_id(inputNumber) .expect(#inputNumber should exist) .dyn_ref::HtmlInputElement() .expect(#inputNumber should be a HtmlInputElement) .set_oninput(Some(callback.as_ref().unchecked_ref())); // Leaks memory. callback.forget(); }这段代码里有三个 wasm-bindgen 实战中极易踩坑的点Closure::new与forget()wasm_bindgen::Closure包装一个 Rust 闭包并暴露为 JS 可调用的函数指针。闭包一旦离开 Rust 作用域就可能被析构导致 JS 侧回调失效所以最后调用callback.forget()显式泄漏内存来保活——注释里直接写了 Leaks memory.。这是 wasm-bindgen 处理长期存活回调的标准手法persistent_callback_handle的滚动更新onmessage回调同样不能随作用域销毁而每次都泄漏一个新的闭包又会持续堆积内存。这里的解法是把最近一次get_on_msg_callback()返回的句柄移动进oninput闭包每次发送消息前用新回调替换旧回调persistent_callback_handle get_on_msg_callback();再set_onmessage挂载。这样始终只有最近一次的onmessage回调被持有既保持有效又避免每次泄漏dyn_ref类型向下转换get_element_by_id返回Element需要通过dyn_ref::HtmlInputElement()/dyn_ref::HtmlElement()转换到具体类型后才能调用value()、set_inner_text()等方法。处理 Worker 返回消息get_on_msg_callback/// Create a closure to act on the message returned by the worker fn get_on_msg_callback() - Closuredyn FnMut(MessageEvent) { Closure::new(move |event: MessageEvent| { console::log_2(Received response: .into(), event.data()); let result match event.data().as_bool().unwrap() { true even, false odd, }; let document web_sys::window().unwrap().document().unwrap(); document .get_element_by_id(resultField) .expect(#resultField should exist) .dyn_ref::HtmlElement() .expect(#resultField should be a HtmlInputElement) .set_inner_text(result); }) }返回值类型是Closuredyn FnMut(MessageEvent)一个可多次调用、接收web_sys::MessageEvent的闭包正好匹配Worker.set_onmessage的签名通过event.data().as_bool()解析 Worker 发回的布尔值映射为even/odd写入#resultFieldconsole::log_2打印收到的数据方便在 DevTools 中观察消息往返。页面骨架index.htmlexamples/wasm-in-web-worker/index.html 除了加载两个脚本外还定义了交互所需的两个 DOM 元素div idwrapper h1Main Thread/Wasm Web Worker Interaction/h1 input typetext idinputNumber div idresultField/div /div#inputNumber文本输入框oninput回调挂在这里#resultField结果显示区域用于写入even/odd。样式由 style.css 提供居中布局、黑色背景、白色文字。注意页面里wasm_in_web_worker.js与index.js都是普通脚本无typemodule与no-modules构建目标严格对应。主线程初始化index.jsexamples/wasm-in-web-worker/index.js 只在主线程运行// We only need startup here which is the main entry point // In theory, we could also use all other functions/struct types from Rust which we have bound with // #[wasm_bindgen] const {startup} wasm_bindgen; async function run_wasm() { // Load the Wasm file by awaiting the Promise returned by wasm_bindgen // wasm_bindgen was imported in index.html await wasm_bindgen(); console.log(index.js loaded); // Run main Wasm entry point // This will create a worker from within our Rust code compiled to Wasm startup(); } run_wasm();从全局对象wasm_bindgen中解构出startupawait wasm_bindgen()wasm_bindgen本身是一个异步初始化函数返回 Promiseresolve 后 Wasm 模块才可用注意这里不传参数glue 会按默认方式加载同目录下的_bg.wasm之后调用startup()Rust 代码随即开始创建 Worker。Worker 侧初始化worker.jsexamples/wasm-in-web-worker/worker.js 展示了 Worker 环境中加载 Wasm 的标准姿势// The worker has its own scope and no direct access to functions/objects of the // global scope. We import the generated JS file to make wasm_bindgen // available which we need to initialize our Wasm code. importScripts(./wasm_in_web_worker.js); console.log(Initializing worker) // In the worker, we have a different struct that we want to use as in // index.js. const {NumberEval} wasm_bindgen; async function init_wasm_in_worker() { // Load the Wasm file by awaiting the Promise returned by wasm_bindgen. await wasm_bindgen(./wasm_in_web_worker_bg.wasm); // Create a new object of the NumberEval struct. var num_eval NumberEval.new(); // Set callback to handle messages passed to the worker. self.onmessage async event { // By using methods of a struct as reaction to messages passed to the // worker, we can preserve our state between messages. var worker_result num_eval.is_even(event.data); // Send response back to be handled by callback in main thread. self.postMessage(worker_result); }; }; init_wasm_in_worker();四个关键点importScripts(./wasm_in_web_worker.js)Worker 作用域没有window也没有script标签可用必须用importScripts加载 glue 脚本把wasm_bindgen全局对象引入 Worker。这正是--target no-modules的产物能够一套代码两边用的原因显式传入 wasm 路径await wasm_bindgen(./wasm_in_web_worker_bg.wasm)。与index.js中不传参不同这里明确指定 Wasm 二进制路径因为 Worker 内的相对路径解析基准与主线程不同显式传入更稳妥状态对象在 Worker 中创建NumberEval.new()在 Worker 内实例化self.onmessage中调用num_eval.is_even(event.data)——由于用的是同一个结构体实例多次消息之间状态得以保持这正是NumberEval注释中强调的用途self.postMessage(worker_result)把布尔结果发回主线程触发主线程中挂载的onmessage回调。消息传递的完整时序综合主线程与 Worker 两侧代码一次完整的消息往返时序如下用户在#inputNumber中输入触发oninput主线程的oninput闭包读取输入值parse::i32()成功则worker_handle.post_message(number.into())发送数字给 Workerparse失败则清空#resultField主线程生成新的onmessage回调并通过set_onmessage挂载异步返回的响应由它处理Worker 的self.onmessage收到数字调用num_eval.is_even(event.data)判断奇偶Worker 通过self.postMessage(worker_result)把布尔结果发回主线程主线程的onmessage回调收到MessageEventevent.data().as_bool()得到布尔值写入even/odd到#resultField。因为 Worker 天然异步、不阻塞主线程这种模式把耗时的后台计算放到了独立线程页面 UI 始终保持响应。关联测试与仓库内验证仓库中还有与本主题直接相关的测试基础设施可以作为读者进一步验证 Worker 场景的入口tests/worker/dedicated.rs使用wasm_bindgen_test_configure!(run_in_dedicated_worker)让测试用例直接跑在专用 Worker 中并声明了一个#[wasm_bindgen(start)]入口注释注明should not be executed用于验证测试框架在 Worker 下的执行隔离tests/worker/modules.rs演示 Worker 环境下通过#[wasm_bindgen(raw_module ./tests/worker/modules.js)]导入外部 JS 模块get_five()并配合modules.js完成跨语言调用测试同目录下还有 service.rs 与 shared.rs分别覆盖 Service Worker 与 Shared Worker 场景说明 wasm-bindgen 的测试体系对多种 Worker 形态均有支持。总结与延伸阅读本示例通过约 130 行 Rust 与 60 行 JS演示了 wasm-bindgen 在 Worker 场景下的全部关键技法--target no-modules构建保证跨浏览器兼容、web_sys::Worker在 Wasm 内创建 Worker、RcRefCell共享句柄、Closureforget保活回调、importScripts在 Worker 内加载 Wasm以及带状态结构体跨消息保持数据。如果想继续探索相关主题仓库中还有以下资源guide/src/examples/without-a-bundler.mdno-modules/web目标的另一套无打包器部署方案examples/synchronous-instantiation同步实例化 Wasm 的 Worker 变体examples/wasm-in-wasm 与 examples/wasm-in-wasm-imports嵌套 Wasm 加载同样涉及 Worker 环境crates/web-sysWorker、MessageEvent等 API 的完整实现与 feature 清单。掌握本文的 Worker 模式后你可以将其扩展到 WebGL 离屏渲染、大规模数据清洗、音视频解码等任何需要主线程不被阻塞的场景。赞分享开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载相关推荐pip 手册Man Page完整指南命令体系、通用选项与 man 文档的自动生成机制pip 手册Man Page完整指南命令体系、通用选项与 man 文档的自动生成机制 本文基于 pip 仓库中的 docs/man/index.rst 文开发工具wasm-bindgen 并行光线追踪在浏览器中驱动多线程 WebAssembly 的完整实战指南wasm bindgen 并行光线追踪在浏览器中驱动多线程 WebAssembly 的完整实战指南 导读 raytrace parallel 是 wasm b开发工具WTF-Solidity 避坑指南67 讲 Solidity 极简教程怎么走完WTF Solidity 避坑指南67 讲 Solidity 极简教程怎么走完 想学 Solidity你搜到的资料往往两头踩空要么上来就讲 EVM、gas示例工程区块链教程上一篇MASA汉化包快速上手3个步骤让Minecraft七个模组全部变成中文下一篇新标签页也能自己做主把新标签页重定向成任意页面这款免费Chrome扩展帮你轻松搞定创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考