怎么为一个通用 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>whereF: 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-unknowncargo check --target wasm32-unknown-unknown
例如,可以将其添加到 Travis CI 的 .travis.yml 配置中:
matrix:include:- language: rustrust: stablename: "check wasm32 support"install: rustup target add wasm32-unknown-unknownscript: cargo check --target wasm32-unknown-unknown
在 Node.js 和 Headless 浏览器中测试
你可以使用 wasm-bindgen test 和 wasm-pack test 子命令在 Node.js 或无头浏览器中运行 wasm 测试。
你甚至可以将这些测试集成到CI中。
