- 开发工具
【免费下载链接】wasm-bindgen
Facilitating high-level interactions between Wasm modules and JavaScript
js-sys是 wasm-bindgen 生态中手写的 ECMAScript 标准全局 API 绑定层,覆盖所有 JavaScript 环境(浏览器、Node.js 等)都保证存在的内置对象与全局函数。本文基于仓库内 guide/src/contributing/js-sys/adding-more-apis.md 的贡献流程,结合 crates/js-sys/src/lib.rs 中的源码级规范,完整讲解"当发现缺失 API 或 TC39 新提案进入 Stage 4 时,如何为 js-sys 新增绑定",读完即可按仓库既定规范独立提交一次高质量的新 API 绑定 PR。
js-sys 的定位:只绑定 ECMAScript 标准全局 API
在动手新增 API 之前,必须先明确 js-sys 的边界。根据 crates/js-sys/README.md 与 crates/js-sys/src/lib.rs 顶部文档注释:
- js-sys 是手写的 JS 全局 API 绑定(raw bindings),不是自动生成;
- 目标是在所有 JS 环境(浏览器、Node.js 等)中可用;
- 不包含任何 Web、Node 或其他 JS 环境专属 API,只包含 ECMAScript 标准保证存在于全局作用域的东西(即 MDN Global Objects 目录 中属于 ECMAScript 标准的那部分)。
例如Array、Promise、Map、Set、decodeURI属于 js-sys 的职责范围,而fetch、XMLHttpRequest、document等 Web API 属于 web-sys 的职责范围。因此新增 API 的第一步是确认它确实属于 ECMAScript 标准而非宿主环境扩展。
何时需要新增 API:触发条件
原文档给出了两个明确触发条件:
- 发现缺失的 API:js-sys 覆盖了 ECMAScript 标准中所有 API,但标准在持续演进,绑定可能出现遗漏;
- 新 API 已到达 TC39 Stage 4:TC39 提案流程中,Stage 4 表示提案已被 ECMAScript 标准委员会正式接受、即将/已经进入标准,此时就值得为它添加绑定。
满足上述条件时,原文档建议先在 GitHub 上提交 issue 进行登记与讨论,再着手实现。新增 API 之前还应当查阅 js-sys 类型参考文档,确认该 API 是否已以泛型类型(如Array<T>、Promise<T>、Map<K, V>等)的形式存在,避免重复添加。
源码级检查清单:lib.rs 中的"新增导入规范"
新增绑定的核心规范并不在单独的文档里,而是以注释形式直接内嵌在 crates/js-sys/src/lib.rs 的// When adding new imports:中,这也是本贡献流程最权威的依据。规范全文如下:
When adding new imports: * Keep imports in alphabetical order. * Rename imports with `js_name = ...` according to the note about `camelCase` and `snake_case` in the module's documentation above. * Include the one sentence summary of the import from the MDN link in the module's documentation above, and the MDN link itself. * If a function or method can throw an exception, make it catchable by adding `#[wasm_bindgen(catch)]`. * Add a new `#[test]` into the appropriate file in the `crates/js-sys/tests/wasm/` directory. If the imported function or method can throw an exception, make sure to also add test coverage for that case. * Arguments that are `JsValue`s or imported JavaScript types should be taken by reference. * Name JavaScript's `toString()` method as `to_js_string()` to avoid conflict with Rust's `ToString` trait.下面逐条展开说明每项规范的意图与仓库中的对应实现。
1. 导入保持字母序
所有extern "C"块中的绑定按字母顺序排列,便于审查与查找。新增绑定时应插入到正确位置,而不是追加在文件末尾。
2. 用js_name处理 camelCase / snake_case 命名差异
JavaScript 的全局对象与方法使用camelCase命名,而 Rust 风格是snake_case。绑定对外暴露 Rust 风格的snake_case名字,同时用#[wasm_bindgen(js_name = "...")]指向真实的 JavaScript 名字。此外,方法名中的缩写(acronym)在 Rust 侧全部小写,而在 JavaScript 中通常全大写。例如decodeURI绑定为decode_uri:
crates/js-sys/src/lib.rs:
#[wasm_bindgen] extern "C" { /// The `decodeURI()` function decodes a Uniform Resource Identifier (URI) /// previously created by `encodeURI` or by a similar routine. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI) #[wasm_bindgen(catch, js_name = decodeURI)] pub fn decode_uri(encoded: &str) -> Result<JsString, JsValue>; /// The `decodeURIComponent()` function decodes a Uniform Resource Identifier (URI) component /// previously created by `encodeURIComponent` or by a similar routine. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent) #[wasm_bindgen(catch, js_name = decodeURIComponent)] pub fn decode_uri_component(encoded: &str) -> Result<JsString, JsValue>; }同理,encodeURI绑定为encode_uri、encodeURIComponent绑定为encode_uri_component(见 crates/js-sys/src/lib.rs)。
3. 附带 MDN 单句摘要与链接
每个绑定的 doc 注释必须包含来自 MDN 的一句话功能摘要,以及对应的MDN 链接。这一点在所有现有绑定中都有体现(如上面的decodeURI示例)。这条规范保证了 js-sys 的文档质量——每个绑定都有官方语义来源,读者无需再翻外部文档。
4. 可抛异常的 API 必须加#[wasm_bindgen(catch)]
如果一个函数或方法可能抛出异常,就必须加上#[wasm_bindgen(catch)],使 Rust 侧签名变成Result<_, JsValue>,从而把 JS 异常转化为 Rust 的错误值而不是 panic 或未定义行为。这在 lib.rs 中大量使用:
- 全局函数:
decode_uri、decode_uri_component返回Result<JsString, JsValue>; - 静态方法:
Array.from、Array.fromAsync等使用#[wasm_bindgen(static_method_of = Array, catch, js_name = from)](见 crates/js-sys/src/lib.rs); - 实例方法:
Array.prototype.every/filter/find/map/sort等大量方法使用#[wasm_bindgen(method, js_name = ..., catch)](见 crates/js-sys/src/lib.rs)。
判断"是否可抛异常"的依据是 ECMAScript 规范与 MDN 文档中该方法是否声明可能抛出(如类型错误、范围错误等)。
5. 参数按引用传递
凡是JsValue或导入的 JavaScript 类型的参数,都应按引用(&T)传递,避免不必要的所有权转移与克隆,这也符合 wasm-bindgen 的 ABI 约定。字符串、数字等 Rust 原始类型则按其自身约定传递。
6.toString()一律命名为to_js_string()
JavaScript 的toString()在 js-sys 中暴露为to_js_string(),这是为了避免与 Rust 标准库的ToStringtrait 及其to_string()方法冲突。这样一来,类型可以同时实现 Rust 的Displaytrait(经由ToString提供to_string())与 JS 侧的toString()功能而不互相干扰。仓库中大量#[wasm_bindgen(method, js_name = toString)]即对应此规范(见 crates/js-sys/src/lib.rs 等处)。同理,valueOf()、toLocaleString()等按需绑定。
绑定结构速查:一个完整绑定长什么样
综合上述规范,一个完整的新绑定通常包含四部分:
/// 来自 MDN 的一句话功能摘要 /// /// [MDN documentation](https://developer.mozilla.org/...) #[wasm_bindgen(catch, js_name = originalJsName)] // 若可抛异常则加 catch pub fn rust_name(args: &JsValue) -> Result<JsValue, JsValue>; // 参数按引用,异常转 Result- 若绑定的是对象静态方法,使用
static_method_of = TypeName; - 若绑定的是实例方法,使用
method; - 若绑定的是命名空间下的函数(如
Atomics),使用js_namespace = Atomics(见 crates/js-sys/src/lib.rs 中大量#[wasm_bindgen(js_namespace = Atomics, catch, js_name = ...)])。
为新 API 添加测试
规范要求每个新增绑定都要在crates/js-sys/tests/wasm/目录下对应的文件中添加新的#[wasm_bindgen_test]测试。测试文件按内置对象/主题组织,例如 crates/js-sys/tests/wasm/Array.rs、Map.rs、Promise.rs、Number.rs、AggregateError.rs等。如果新绑定可能抛异常,还必须同时覆盖异常路径的测试用例。
典型测试结构(摘自 crates/js-sys/tests/wasm/Array.rs):
#[wasm_bindgen_test] fn from_iter() { assert_eq!( to_rust( &vec![JsValue::from("a"), JsValue::from("b"), JsValue::from("c"),] .into_iter() .collect() ), vec!["a", "b", "c"], ); // ... }测试通过#[wasm_bindgen_test]宏标记,编译为 Wasm 后在 Node.js 或无头浏览器中执行。js-sys 的完整测试命令(来自 guide/src/contributing/testing.md):
WASM_BINDGEN_SPLIT_LINKED_MODULES=1 cargo test --target wasm32-unknown-unknown如果你只需要验证 js-sys 相关测试,可在工作区中聚焦对应 crate 执行。测试运行前提是安装好 Rust 的wasm32-unknown-unknowntarget 与支持 WebAssembly 的 Node.js(详见 guide/src/contributing/index.md 的 Prerequisites 部分):
rustup target add wasm32-unknown-unknown常用泛型类型参考:避免重复造轮子
新增绑定前,务必对照 js-sys 类型参考文档 检查是否已有可复用的泛型绑定。该参考列出了Array<T>、ArrayTuple<T1..T8>、Function<fn(A) -> R>、Promise<T>、Map<K, V>、Set<T>、Iterator<T>/AsyncIterator<T>、Generator<T>、Object<T>、WeakMap/WeakSet/WeakRef、JsOption<T>(T | undefined)与JsNullable<T>(WebIDLT?)等泛型类型。所有泛型类型都实现JsGeneric,未指定类型参数时默认JsValue。
举例来说,如果你要绑定一个返回 Promise 的新 API,应声明返回Promise<T>而非裸Promise,从而让调用方直接.await得到类型化的T;如果你要绑定一个可能返回undefined的取值函数,应使用JsOption<T>而不是Option<T>,因为JsOption<T>可以在JsGeneric位置延迟 undefined 检查(两者行为对比见参考文档中的表格)。
提交前的自查清单
综合原文档与源码规范,提交新增 API 的 PR 前应逐项确认:
- API 属于 ECMAScript 标准全局 API(而非 Web/Node 专属 API);
- 若为新提案,已到 TC39 Stage 4;若为缺失绑定,已提交 issue 说明;
- 绑定按字母序插入,
js_name正确映射 camelCase 到 snake_case; - doc 注释含 MDN 单句摘要与链接;
- 可抛异常的 API 已加
#[wasm_bindgen(catch)]并返回Result<_, JsValue>; JsValue/导入类型参数按引用传递;toString()命名为to_js_string();- 已在
crates/js-sys/tests/wasm/对应文件添加测试(含异常路径); - 本地
cargo test --target wasm32-unknown-unknown通过。
延伸阅读
- js-sys 类型参考:泛型绑定与类型擦除的完整说明
- js-sys 测试指南:js-sys 测试入口
- 贡献总览:环境准备与代码格式化要求
- web-sys 贡献指南:Web API 绑定属于 web-sys 而非 js-sys
- js-sys 测试集:全部现有绑定测试,新增测试的样板来源
- js-sys 源码:绑定规范与命名约定的权威出处
- 开发工具
【免费下载链接】wasm-bindgen
Facilitating high-level interactions between Wasm modules and JavaScript
相关推荐
wasm-bindgen 的 web-sys 指南:Web API 原始绑定、Cargo Feature 门控与新增接口的完整流程
wasm bindgen 的 web sys 指南:Web API 原始绑定、Cargo Feature 门控与新增接口的完整流程 导读 web sys 是 w
开发工具为 `web-sys` 扩展新的 Web API:从 WebIDL 到 Rust 绑定的完整贡献指南
为 web sys 扩展新的 Web API:从 WebIDL 到 Rust 绑定的完整贡献指南 web sys 是 wasm bindgen 生态中面向 We
开发工具wasm-bindgen 的 js-sys crate:ECMAScript 全局 API 绑定与类型化泛型系统深度解析
wasm bindgen 的 js sys crate:ECMAScript 全局 API 绑定与类型化泛型系统深度解析 导读 js sys 是 wasm bi
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考