wasmworker
wasmworker is a library that provides easy access to parallelization on web targets when compiled to WebAssembly using wasm-bindgen.
In contrast to many other libraries like wasm-bindgen-rayon, this library does not require SharedArrayBuffer support.
Usage
The library consists of two crates:
wasmworker: The main crate that also offers access to the webworker, as well as the worker pool and iterator extensions.wasmworker-proc-macro: This crate is needed to expose functions towards the web workers via the#[webworker_fn]macro.
Setting up
To use this library, include both dependencies to your Cargo.toml.
[]
= "0.2"
= "0.2"
The wasmworker crate comes with a default feature called serde, which allows running any function on a web worker under the following two conditions:
- The function takes a single argument, which implements
serde::Serialize + serde::Deserialize<'de>. - The return type implements
serde::Serialize + serde::Deserialize<'de>.
Without the serde feature, only functions with the type fn(Box<[u8]>) -> Box<[u8]> can be run on a worker.
This is useful for users that do not want a direct serde dependency. Internally, the library always uses serde, though.
You can then start using the library without further setup.
If you plan on using the global WebWorkerPool (using the iterator extensions or worker_pool()), you can optionally configure this pool:
// Importing it publicly will also expose the function on the JavaScript side.
// You can instantiate the pool both via Rust and JS.
pub use ;
async
#
Outsourcing tasks
The library offers three ways of outsourcing function calls onto concurrent workers:
WebWorker: a single worker, to which tasks can be queued to.WebWorkerPool: a pool of multiple workers, to which tasks are distributed.par_map: an extension to regular iterators, which allows to execute a function on every element of the iterator in parallel using the default worker pool.
All approaches require the functions that should be executed to be annotated with the #[webworker_fn] macro.
This macro ensures that the functions are available to the web worker instances:
use ;
use webworker_fn;
/// An arbitrary type that is (de)serializable.
;
/// A sort function on a custom type.
#
Whenever we want to execute a function, we need to pass the corresponding WebWorkerFn object to the worker.
This object describes the function to the worker and can be safely obtained via the webworker!() macro:
# use ;
# use webworker_fn;
#
# ;
#
#
use webworker;
#
WebWorker
We can instantiate our own workers and run functions on them:
# use ;
# use webworker_fn;
#
# ;
#
#
use ;
# async
#
WebWorkerPool
Most of the time, we probably want to schedule tasks to a pool of workers, though.
The default worker pool is instantiated on first use and can be configured using init_worker_pool() as described above.
It uses a round-robin scheduler (with the second option being a load based scheduler), a number of navigator.hardwareConcurrency separate workers, and the default inferred path.
# use ;
# use webworker_fn;
#
# ;
#
#
use ;
# async
#
Iterator extension
Inspired by Rayon, this library also offers a (much simpler and less powerful) method for iterators. This functionality automatically parallelizes a map operation on the default worker pool.
use IteratorExt;
let some_vec = vec!;
let res: = some_vec.iter.par_map.await;
Async functions with channels
For more complex use cases like progress reporting or interactive workflows, you can use async functions with bidirectional channel support.
First, define an async function with the #[webworker_channel_fn] macro:
use Channel;
use webworker_channel_fn;
pub async
Then use the webworker_channel! macro and run_channel method:
use ;
let worker = new.await?;
// Create a channel for bidirectional communication
let = new?;
// Start the async task
let task = worker.run_channel;
// Receive progress from worker
let progress: Progress = main_channel.recv.await.unwrap;
// Send response back to worker
main_channel.send;
// Wait for task completion
let result = task.await;
Bundler support (Vite)
The recommended approach for Vite is to place the wasm-pack output in Vite's publicDir.
This keeps the glue code and WASM binary as static assets, which is required because each
WebWorker needs to import the glue code independently via import().
A working example is in test/vite-app/.
1. Build with wasm-pack:
2. Configure Vite (vite.config.js):
import from 'vite';
export default ;
3. Load in your entry point (index.js):
// Dynamic import with @vite-ignore to skip Vite's module resolution
const = await import;
await ;
No Rust-side changes are needed — import.meta.url resolves correctly when the glue code is served as a static asset.
Advanced: custom paths
If your build setup places the wasm-bindgen glue or WASM binary at non-standard locations (e.g., hashed filenames, nested directories), you can override the paths explicitly:
use ;
# async
#
Precompiling WASM
To reduce bandwidth (fetch WASM once instead of once per worker), you can precompile and share the module:
# use ;
# async
#
FAQ
-
Why would you not want to use SharedArrayBuffers?
The use of SharedArrayBuffers requires cross-origin policy headers to be set, which is not possible in every environment. Moreover, most libraries that rely on SharedArrayBuffers, also require a nightly version of Rust at the moment. An important goal of this library is to remove these requirements.
-
Which
wasm-bindgentargets are supported?So far, this library has only been tested with
--target web. Other targets seem to generally be problematic in that the wasm glue is inaccessible or paths are not correct. Both theWorkerandWebWorkerPoolhave an option to set a custom path, which should make it possible to support other targets dynamically, though. -
Can I use bundlers?
Yes! Vite is tested and supported. The recommended approach is to serve the wasm-pack output as static assets (via Vite's
publicDir) rather than bundling it. This ensures each WebWorker can import the glue code independently. See Bundler support (Vite) for a step-by-step guide andtest/vite-app/for a working example.