怎么为一个通用 crate 添加 WebAssembly 支持

本节适用于,希望支持 WebAssembly 的通用 crate 作者。

也许你的 crate 已经支持 WebAssembly 了!

查看关于什么样的东西可以使通用 crate 不能用于 WebAssembly 的信息 。 如果你的 crate 没有这些东西,它可能已经支持 WebAssembly 了!

可以通过运行 WebAssembly 目标的 `cargo build 进行检查:

  1. cargo build --target wasm32-unknown-unknown

如果该命令失败,那么你的 crate 将不支持 WebAssembly。 如果它没有失败,那么你的 crate 很可能支持 WebAssembly。 你可以百分之百地肯定它会这样做(而且还会继续这样做!)通过为 wasm 添加测试并在 CI 中运行这些测试。

添加对 WebAssembly 的支持

避免直接执行 I/O

在 Web 上,I/O 总是异步的,并且没有文件系统。 从库中取出 I/O,让用户执行 I/O,然后将输入片段传递到库中。

例如,要重构:

  1. use std::fs;
  2. use std::path::Path;
  3. pub fn parse_thing(path: &Path) -> Result<MyThing, MyError> {
  4. let contents = fs::read(path)?; // <- IO 操作
  5. // ...
  6. }

对此:

  1. pub fn parse_thing(contents: &[u8]) -> Result<MyThing, MyError> {
  2. // .............⬆️ 替换成了数组
  3. }

添加依赖 wasm-bindgen

如果你需要与外界交流(而你不能让库的使用者为你进行这种操作),那你需要加上 wasm-bindgen(以及 js-sysweb-sys,如果需要)作为编译以 WebAssembly 为目标时的依赖项:

  1. [target.'cfg(target_arch = "wasm32")'.dependencies]
  2. wasm-bindgen = "0.2"
  3. js-sys = "0.3"
  4. web-sys = "0.3"

避免同步 I/O

如果必须在库中执行 I/O,则它不能是同步的。 Web 上只有异步 I/O。 使用 futures cratewasm-bindgen-futures crate 来管理异步 I/O。 如果你的库函数是某个 future 类型 F 的通用函数,那么可以通过 Web 上的 fetch 或操作系统提供的非阻塞 I/O 来实现 future 类型 F

  1. pub fn do_stuff<F>(future: F) -> impl Future<Item = MyOtherThing>
  2. where
  3. F: Future<Item = MyThing>,
  4. {
  5. // ...
  6. }

你还可以为 WebAssembly 和 Web 以及本机目标定义并实现 trait:

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

避免产生线程

Wasm 还不支持线程(但是实验工作正在进行),因此在 Wasm 中生成线程的尝试会引起 panic。

根据目标是否为 WebAssembly,可以使用 #[cfg(..)] 启用线程和非线程代码路径:

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

另一种选择是将从库中派生的线程分解出来,并允许用户“自带线程”,类似于分解文件 I/O 并允许用户自带 I/O。 这有一个副作用,那就是与想要拥有自己的自定义线程池的应用程序打交道。

维护对 WebAssembly 的持续支持

在 CI 中构建 wasm32-unknown-unknown

通过让 CI 脚本运行以下命令,确保以 WebAssembly 为目标时编译不会失败:

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

例如,可以将其添加到 Travis CI 的 .travis.yml 配置中:

  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

在 Node.js 和 Headless 浏览器中测试

你可以使用 wasm-bindgen testwasm-pack test 子命令在 Node.js 或无头浏览器中运行 wasm 测试。 你甚至可以将这些测试集成到CI中。

了解更多关于测试 wasm 的信息。