How to Add WebAssembly Support to a General-Purpose Crate
This section is for general-purpose crate authors who want to support WebAssembly.
Maybe Your Crate Already Supports WebAssembly!
Review the information about what kinds of things can make a general-purpose crate not portable for WebAssembly. If your crate doesn't have any of those things, it likely already supports WebAssembly!
You can always check by running cargo build
for the WebAssembly target:
cargo build --target wasm32-unknown-unknown
If that command fails, then your crate doesn't support WebAssembly right now. If it doesn't fail, then your crate might support WebAssembly. You can be 100% sure that it does (and continues to do so!) by adding tests for wasm and running those tests in CI.
Adding Support for WebAssembly
Avoid Performing I/O Directly
On the Web, I/O is always asynchronous, and there isn't a file system. Factor I/O out of your library, let users perform the I/O and then pass the input slices to your library instead.
For example, refactor this:
# #![allow(unused_variables)] #fn main() { use std::fs; use std::path::Path; pub fn parse_thing(path: &Path) -> Result<MyThing, MyError> { let contents = fs::read(path)?; // ... } #}
Into this:
# #![allow(unused_variables)] #fn main() { pub fn parse_thing(contents: &[u8]) -> Result<MyThing, MyError> { // ... } #}
Add wasm-bindgen
as a Dependency
If you need to interact with the outside world (i.e. you can't have library
consumers drive that interaction for you) then you'll need to add wasm-bindgen
(and js-sys
and web-sys
if you need them) as a dependency for when
compilation is targeting WebAssembly:
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = "0.3"
Avoid Synchronous I/O
If you must perform I/O in your library, then it cannot be synchronous. There is
only asynchronous I/O on the Web. Use the futures
crate and the wasm-bindgen-futures
crate to
manage asynchronous I/O. If your library functions are generic over some
future type F
, then that future can be implemented via fetch
on the Web or
via non-blocking I/O provided by the operating system.
# #![allow(unused_variables)] #fn main() { pub fn do_stuff<F>(future: F) -> impl Future<Item = MyOtherThing> where F: Future<Item = MyThing>, { // ... } #}
You can also define a trait and implement it for WebAssembly and the Web and also for native targets:
# #![allow(unused_variables)] #fn main() { trait ReadMyThing { type F: Future<Item = MyThing>; fn read(&self) -> Self::F; } #[cfg(target_arch = "wasm32")] struct WebReadMyThing { // ... } #[cfg(target_arch = "wasm32")] impl ReadMyThing for WebReadMyThing { // ... } #[cfg(not(target_arch = "wasm32"))] struct NativeReadMyThing { // ... } #[cfg(not(target_arch = "wasm32"))] impl ReadMyThing for NativeReadMyThing { // ... } #}
Avoid Spawning Threads
Wasm doesn't support threads yet (but experimental work is ongoing), so attempts to spawn threads in wasm will panic.
You can use #[cfg(..)]
s to enable threaded and non-threaded code paths
depending on if the target is WebAssembly or not:
# #![allow(unused_variables)] #![cfg(target_arch = "wasm32")] #fn main() { fn do_work() { // Do work with only this thread... } #![cfg(not(target_arch = "wasm32"))] fn do_work() { use std::thread; // Spread work to helper threads.... thread::spawn(|| { // ... }); } #}
Another option is to factor out thread spawning from your library and allow users to "bring their own threads" similar to factoring out file I/O and allowing users to bring their own I/O. This has the side effect of playing nice with applications that want to own their own custom thread pool.
Maintaining Ongoing Support for WebAssembly
Building for wasm32-unknown-unknown
in CI
Ensure that compilation doesn't fail when targeting WebAssembly by having your CI script run these commands:
rustup target add wasm32-unknown-unknown
cargo check --target wasm32-unknown-unknown
For example, you can add this to your .travis.yml
configuration for Travis CI:
matrix:
include:
- language: rust
rust: stable
name: "check wasm32 support"
install: rustup target add wasm32-unknown-unknown
script: cargo check --target wasm32-unknown-unknown
Testing in Node.js and Headless Browsers
You can use wasm-bindgen-test
and the wasm-pack test
subcommand to run wasm
tests in either Node.js or a headless browser. You can even integrate these
tests into your CI.