如何为通用板条箱添加 WebAssembly 支持

本节面向希望支持 WebAssembly 的通用板条箱作者。

您的板条箱可能已经支持 WebAssembly!

查看有关 哪些因素会导致通用板条箱无法移植到 WebAssembly 的信息。如果您的板条箱没有任何这些因素,它可能已经支持 WebAssembly!

您可以始终通过为 WebAssembly 目标运行 cargo build 来检查

cargo build --target wasm32-unknown-unknown

如果该命令失败,则您的板条箱目前不支持 WebAssembly。如果它没有失败,则您的板条箱可能支持 WebAssembly。您可以通过 为 wasm 添加测试并在 CI 中运行这些测试 来 100% 确定它确实支持(并且继续支持)WebAssembly!

添加对 WebAssembly 的支持

避免直接执行 I/O

在 Web 上,I/O 始终是异步的,并且没有文件系统。将 I/O 从您的库中分离出来,让用户执行 I/O,然后将输入切片传递给您的库。

例如,重构以下代码


# #![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)?;
    // ...
}
#}

重构为以下代码


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

添加 wasm-bindgen 作为依赖项

如果您需要与外部世界交互(即您不能让库使用者为您驱动这种交互),那么您需要添加 wasm-bindgen(以及 js-sysweb-sys,如果您需要它们)作为依赖项,以便在编译针对 WebAssembly 时使用。

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

避免同步 I/O

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


# #![allow(unused_variables)]
#fn main() {
pub fn do_stuff<F>(future: F) -> impl Future<Item = MyOtherThing>
where
    F: Future<Item = MyThing>,
{
    // ...
}
#}

您还可以定义一个特征并为 WebAssembly 和 Web 以及本地目标实现它。


# #![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 {
    // ...
}
#}

避免生成线程

Wasm 尚未支持线程(但 正在进行实验性工作),因此尝试在 wasm 中生成线程将导致恐慌。

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


# #![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(|| {
        // ...
    });
}
#}

另一种选择是将线程生成从您的库中分离出来,并允许用户“自带线程”,类似于将文件 I/O 分离出来并允许用户自带 I/O。这具有与希望拥有自己的自定义线程池的应用程序配合使用的副作用。

维护对 WebAssembly 的持续支持

在 CI 中为 wasm32-unknown-unknown 构建

通过让 CI 脚本运行以下命令,确保编译在针对 WebAssembly 时不会失败。

rustup target add wasm32-unknown-unknown
cargo check --target wasm32-unknown-unknown

例如,您可以将此添加到 Travis CI 的 .travis.yml 配置中。


matrix:
  include:
    - language: rust
      rust: stable
      name: "check wasm32 support"
      install: rustup target add wasm32-unknown-unknown
      script: cargo check --target wasm32-unknown-unknown

在 Node.js 和无头浏览器中进行测试

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

在此处了解有关测试 wasm 的更多信息。