How to Add WebAssembly Support to a General-Purpose Crate
This section is for general-purpose crate authors who want to supportWebAssembly.
Maybe Your Crate Already Supports WebAssembly!
Review the information about what kinds of things can make a general-purposecrate not portable for WebAssembly. Ifyour crate doesn't have any of those things, it likely already supportsWebAssembly!
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. Ifit 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 andrunning 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. FactorI/O out of your library, let users perform the I/O and then pass the inputslices 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 libraryconsumers 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 whencompilation 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 isonly asynchronous I/O on the Web. Use the futures
crate and the wasm-bindgen-futures
crate tomanage asynchronous I/O. If your library functions are generic over somefuture type F
, then that future can be implemented via fetch
on the Web orvia 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 andalso 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 isongoing),so attempts to spawn threads in wasm will panic.
You can use #[cfg(..)]
s to enable threaded and non-threaded code pathsdepending 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 allowusers to "bring their own threads" similar to factoring out file I/O andallowing users to bring their own I/O. This has the side effect of playing nicewith 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 yourCI 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 wasmtests in either Node.js or a headless browser. You can even integrate thesetests into your CI.