怎么为一个通用 crate 添加 WebAssembly 支持
本节适用于,希望支持 WebAssembly 的通用 crate 作者。
也许你的 crate 已经支持 WebAssembly 了!
查看关于什么样的东西可以使通用 crate 不能用于 WebAssembly 的信息 。 如果你的 crate 没有这些东西,它可能已经支持 WebAssembly 了!
可以通过运行 WebAssembly 目标的 `cargo build 进行检查:
cargo build --target wasm32-unknown-unknown
如果该命令失败,那么你的 crate 将不支持 WebAssembly。 如果它没有失败,那么你的 crate 很可能支持 WebAssembly。 你可以百分之百地肯定它会这样做(而且还会继续这样做!)通过为 wasm 添加测试并在 CI 中运行这些测试。
添加对 WebAssembly 的支持
避免直接执行 I/O
在 Web 上,I/O 总是异步的,并且没有文件系统。 从库中取出 I/O,让用户执行 I/O,然后将输入片段传递到库中。
例如,要重构:
use std::fs;
use std::path::Path;
pub fn parse_thing(path: &Path) -> Result<MyThing, MyError> {
let contents = fs::read(path)?; // <- IO 操作
// ...
}
对此:
pub fn parse_thing(contents: &[u8]) -> Result<MyThing, MyError> {
// .............⬆️ 替换成了数组
}
添加依赖 wasm-bindgen
如果你需要与外界交流(而你不能让库的使用者为你进行这种操作),那你需要加上 wasm-bindgen
(以及 js-sys
和 web-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
crate 和 wasm-bindgen-futures
crate 来管理异步 I/O。
如果你的库函数是某个 future 类型 F
的通用函数,那么可以通过 Web 上的 fetch
或操作系统提供的非阻塞 I/O 来实现 future 类型 F
。
pub fn do_stuff<F>(future: F) -> impl Future<Item = MyOtherThing>
where
F: Future<Item = MyThing>,
{
// ...
}
你还可以为 WebAssembly 和 Web 以及本机目标定义并实现 trait:
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 中生成线程的尝试会引起 panic。
根据目标是否为 WebAssembly,可以使用 #[cfg(..)]
启用线程和非线程代码路径:
#![cfg(target_arch = "wasm32")]
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 和 Headless 浏览器中测试
你可以使用 wasm-bindgen test
和 wasm-pack test
子命令在 Node.js 或无头浏览器中运行 wasm 测试。
你甚至可以将这些测试集成到CI中。