Wasm in Web Worker
A simple example of parallel execution by spawning a web worker with web_sys
,
loading Wasm code in the web worker and interacting between the main thread and
the worker.
Building & compatibility
At the time of this writing, only Chrome supports modules in web workers, e.g.
Firefox does not. To have compatibility across browsers, the whole example is
set up without relying on ES modules as target. Therefore we have to build
with --target no-modules
. The full command can be found in build.sh
.
Cargo.toml
The Cargo.toml
enables features necessary to work with the DOM, log output to
the JS console, creating a worker and reacting to message events.
[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"
[lints]
workspace = true
src/lib.rs
Creates a struct NumberEval
with methods to act as stateful object in the
worker and function startup
to be launched in the main thread. Also includes
internal helper functions setup_input_oninput_callback
to attach a
wasm_bindgen::Closure
as callback to the oninput
event of the input field
and get_on_msg_callback
to create a wasm_bindgen::Closure
which is triggered
when the worker returns a message.
index.html
Includes the input element #inputNumber
to type a number into and a HTML
element #resultField
were the result of the evaluation even/odd is written to.
Since we require to build with --target no-modules
to be able to load Wasm
code in the worker across browsers, the index.html
also includes loading
both wasm_in_web_worker.js
and index.js
.
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="wrapper">
<h1>Main Thread/Wasm Web Worker Interaction</h1>
<input type="text" id="inputNumber">
<div id="resultField"></div>
</div>
<!-- Make `wasm_bindgen` available for `index.js` -->
<script src='./pkg/wasm_in_web_worker.js'></script>
<!-- Note that there is no `type="module"` in the script tag -->
<script src="./index.js"></script>
</body>
</html>
index.js
Loads our Wasm file asynchronously and calls the entry point startup
of the
main thread which will create a worker.
// 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();
worker.js
Loads our Wasm file by first importing wasm_bindgen
via
importScripts('./pkg/wasm_in_web_worker.js')
and then awaiting the Promise
returned by wasm_bindgen(...)
. Creates a new object to do the background
calculation and bind a method of the object to the onmessage
callback of the
worker.
// 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('./pkg/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('./pkg/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();