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:

  1. 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:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. use std::fs;
  4. use std::path::Path;
  5. pub fn parse_thing(path: &Path) -> Result<MyThing, MyError> {
  6. let contents = fs::read(path)?;
  7. // ...
  8. }
  9. #}

Into this:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. pub fn parse_thing(contents: &[u8]) -> Result<MyThing, MyError> {
  4. // ...
  5. }
  6. #}

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:

  1. [target.'cfg(target_arch = "wasm32")'.dependencies]
  2. wasm-bindgen = "0.2"
  3. js-sys = "0.3"
  4. 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 futurescrate and the wasm-bindgen-futurescrate 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.

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. pub fn do_stuff<F>(future: F) -> impl Future<Item = MyOtherThing>
  4. where
  5. F: Future<Item = MyThing>,
  6. {
  7. // ...
  8. }
  9. #}

You can also define a trait and implement it for WebAssembly and the Web andalso for native targets:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. trait ReadMyThing {
  4. type F: Future<Item = MyThing>;
  5. fn read(&self) -> Self::F;
  6. }
  7. #[cfg(target_arch = "wasm32")]
  8. struct WebReadMyThing {
  9. // ...
  10. }
  11. #[cfg(target_arch = "wasm32")]
  12. impl ReadMyThing for WebReadMyThing {
  13. // ...
  14. }
  15. #[cfg(not(target_arch = "wasm32"))]
  16. struct NativeReadMyThing {
  17. // ...
  18. }
  19. #[cfg(not(target_arch = "wasm32"))]
  20. impl ReadMyThing for NativeReadMyThing {
  21. // ...
  22. }
  23. #}

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:

  1. # #![allow(unused_variables)]
  2. #![cfg(target_arch = "wasm32")]
  3. #fn main() {
  4. fn do_work() {
  5. // Do work with only this thread...
  6. }
  7. #![cfg(not(target_arch = "wasm32"))]
  8. fn do_work() {
  9. use std::thread;
  10. // Spread work to helper threads....
  11. thread::spawn(|| {
  12. // ...
  13. });
  14. }
  15. #}

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:

  1. rustup target add wasm32-unknown-unknown
  2. cargo check --target wasm32-unknown-unknown

For example, you can add this to your .travis.yml configuration for Travis CI:

  1. matrix:
  2. include:
  3. - language: rust
  4. rust: stable
  5. name: "check wasm32 support"
  6. install: rustup target add wasm32-unknown-unknown
  7. 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.

Learn more about testing wasmhere.