- 开发工具
【免费下载链接】wasm-bindgen
Facilitating high-level interactions between Wasm modules and JavaScript
导读
web-sys是 wasm-bindgen 生态中面向 Web 平台的"原始绑定"库,它为几乎所有浏览器 Web API(DOM、WebGL、WebAudio、WebGPU、WebRTC、IndexedDB 等)提供了 Rust 侧的#[wasm_bindgen]导入。本文以 crates/web-sys/README.md 为主线,完整讲解 web-sys 的定位、以"Cargo Feature 逐个门控 API"为核心的设计哲学,以及当你需要使用的 Web API 尚未被收录时,如何利用wasm-bindgen-webidl生成器把 WebIDL 变成 Rust 绑定的六步流程。读完本文,你将掌握 web-sys 的依赖配置、Feature 启用规则、继承层次的访问方式,以及从 WebIDL 到生成代码的底层运作原理。
一、web-sys 是什么:面向 wasm-bindgen 的 Web API 原始绑定
web-sys的定位在 crates/web-sys/README.md 开头一句话即可概括:Raw bindings to Web APIs for projects usingwasm-bindgen(为使用 wasm-bindgen 的项目提供 Web API 的原始绑定)。
它覆盖的 API 范围非常广,包括但不限于:
window.fetchNode.prototype.appendChild- WebGL
- WebAudio
- 以及更多浏览器标准 API
社区常常用一句话类比它的地位:web-sys 之于 Web,就像libc之于操作系统——它是一层尽可能完整、贴近底层的系统级绑定,不掺杂高层次的封装逻辑。
需要特别区分的是,web-sys不包含ECMAScript 标准环境中必然存在的那部分 JavaScript API(例如Array、Date、eval)。这些与具体宿主环境无关的 JS 内置对象绑定,由同仓库的js-syscrate 提供(见 guide/src/web-sys/index.md 与 crates/js-sys/)。也就是说:
- js-sys:标准 ECMAScript 环境的内置对象(
Array、Date、Promise、JSON…),与浏览器无关; - web-sys:浏览器/Web 平台独有 API(DOM、媒体、图形、网络、传感器…),依赖 js-sys 作为基础类型层。
两者在依赖上也是分层关系:crates/web-sys/Cargo.toml 中web-sys以default-features = false的方式依赖js-sys与wasm-bindgen,并通过stdFeature 向上传递wasm-bindgen/std与js-sys/std。
二、Cargo Feature 门控:每个类型一个 Feature
2.1 设计规则:默认几乎为空
web-sys 最核心的设计约束是编译速度:它暴露的 API 数量极其庞大,如果全部编译,构建时间将难以接受。因此 crates/web-sys/README.md 明确说明:
This crate by default contains very little when compiled as almost all of its exposed APIs are gated by Cargo features.
默认情况下 web-sys 几乎不包含任何内容,所有公开 API 都被 Cargo Feature 门控。规则非常简单直接(规则之拇指):
- 每个类型拥有一个以自己名字命名的 Feature;
- 使用某个 API,需要同时启用该 API 涉及的全部类型的 Feature;
- 每个 API 的文档都会注明它依赖哪些 Feature。
从 crates/web-sys/Cargo.toml 的[features]段可以看到这一规则的具体形态。例如:
default = ["std"] std = ["wasm-bindgen/std", "js-sys/std"] AbortController = [] AbortSignal = ["EventTarget"] Event = [] EventTarget = [] Window = ["EventTarget"] WebSocket = ["EventTarget"] Document = ["EventTarget", "Node"] Element = ["EventTarget", "Node"] HtmlAnchorElement = ["Element", "EventTarget", "HtmlElement", "Node"] Node = ["EventTarget"]2.2 Feature 之间的依赖传递
上例中AbortSignal = ["EventTarget"]这种写法揭示了 web-sys 的 Feature 依赖模型:一个类型的 Feature 会自动传递启用其**父类型(继承链上的祖先)**的 Feature。这在 guide/src/web-sys/cargo-features.md 中有专门阐述:
要访问某个类型,必须启用它的 Feature;要访问某个方法,必须启用其
self类型的 Feature,以及每个参数类型的 Feature。
以文档中的经典例子WebGlRenderingContext::compile_shader为例,它要求:
WebGlRenderingContext:因为它是该方法的self类型;WebGlShader:因为该方法接收一个WebGlShader参数。
因此,Feature 列表并非孤立存在,而是形成一张依赖图,启用一个叶子类型的 Feature 时,其继承链上所有祖先类型的 Feature 都会被连带启用,从而保证Deref到父类后调用父类方法时类型依然可用。这正是 crates/web-sys/Cargo.toml 中大量条目携带方括号依赖(如HtmlDivElement = ["Element", "EventTarget", "HtmlElement", "Node"]、AnalyserNode = ["AudioNode", "EventTarget"])的原因。
2.3 从源码看 Feature 的生成与维护
需要强调的是,这份动辄数百行的 Feature 列表不是手写的。crates/web-sys/Cargo.toml 在[features]段上方有一行注释:
# This list is auto-generated by the wasm-bindgen-webidl program即该列表由wasm-bindgen-webidl程序自动生成。查看 crates/webidl/src/main.rs 可知,wasm-bindgen-webidl的入口接收input_dir(WebIDL 输入目录)与output_dir(生成的 Rust 代码输出目录),可选地接收cargo_toml_path,在生成绑定代码之后调用update_cargo_toml_features把新接口的 Feature 写回 Cargo.toml。另外它还支持两个 CLI 开关:
--no-features:生成时不产出 Feature 门控,同时跳过 Cargo.toml 的更新;--next-unstable:配合下一节介绍的不稳定 API 机制使用。
三、快速上手:三步接入 web-sys
第一步:添加依赖
在Cargo.toml中声明web-sys依赖(完整示例见 guide/src/web-sys/using-web-sys.md):
[dependencies] wasm-bindgen = "0.2" [dependencies.web-sys] version = "0.3" features = [ ]第二步:按需启用 Feature
因为 Feature 门控机制的存在,Feature 列表通常是空的起步状态,随后按你实际用到的 API 逐个补充。查找方式是:在 API 文档中定位你要用的类型或方法,文档会列出其必需的 Feature。例如要使用window.resizeTo(即 Rust 侧的web_sys::Window::resize_to),需要启用WindowFeature:
[dependencies.web-sys] version = "0.3" features = [ "Window" ]第三步:调用方法
use wasm_bindgen::prelude::*; use web_sys::Window; #[wasm_bindgen] pub fn make_the_window_small() { // Resize the window to 500px by 500px. let window = web_sys::window().unwrap(); window.resize_to(500, 500) .expect("could not resize the window"); }注意resize_to返回Result——这正是 crates/web-sys/README.md 中"对可能抛错的函数标注[Throws]"这一约定在生成代码层面的体现:WebIDL 中标了[Throws]的接口,生成的 Rust 方法会返回Result,调用方必须处理可能的 JS 异常。
四、使用继承层次:Deref、AsRef 与 JsCast
DOM 的核心工作方式就是 JS 类之间的继承,web-sys 通过三种方式把这一层次暴露给 Rust 开发者(详见 guide/src/web-sys/inheritance.md)。
4.1 用Deref向上访问父类
与 Rust 智能指针类似,web-sys 中所有类型都对其父 JS 类实现Deref。因此拿到一个web_sys::Element,就能隐式地把它当web_sys::Node使用:
let element: &Element = ...; element.append_child(..); // 调用 Node 上的方法 method_expecting_a_node(&element); // 隐式协变到 &Node let node: &Node = &element; // 显式协变到 &Node借助Deref,你可以顺着继承链一路向上,用.运算符直接访问祖先类的所有方法。
4.2 用AsRef显式取父类引用
除Deref外,web-sys 为继承链上每一层祖先都实现了AsRef。以HtmlAnchorElement为例(guide/src/web-sys/inheritance.md):
impl AsRef<HtmlElement> for HtmlAnchorElement impl AsRef<Element> for HtmlAnchorElement impl AsRef<Node> for HtmlAnchorElement impl AsRef<EventTarget> for HtmlAnchorElement impl AsRef<Object> for HtmlAnchorElement impl AsRef<JsValue> for HtmlAnchorElement可以用.as_ref()显式取得任一父类的引用;由于实现数量多,调用时通常需要类型推断来指明目标类型。
4.3 用JsCast向下转型
wasm_bindgen::JsCasttrait 则负责各种类型之间的转换,既支持静态的无检查转换,也支持基于instanceof的运行时动态检查转换。这在把通用类型(如EventTarget)向下转成具体类型(如HtmlInputElement)时至关重要。
五、扩展 web-sys:把新 Web API 添加进来的完整流程
这是 crates/web-sys/README.md 的核心实操章节。当你在 web-sys 中找不到某个 Web API 时,可以按以下六步把它加入。
步骤 1:获取 WebIDL 并放入 unstable 目录
把该 API 的 WebIDL 规范复制到一个新文件,放入webidls/unstable文件夹。如何找到 WebIDL?以 MediaSession API 为例:打开 MDN 对应文档页,滚动到底部点击 "Specifications" 链接进入规范页,再滚动到规范页的最底部即可看到完整的 IDL(IDL Index)。在仓库中,crates/web-sys/webidls/unstable/ 目录下已经放着大量此类文件,例如:
MediaSession.webidlWebGPU.webidlWebXRDevice.webidl、WebXRHandInputModule.webidl、WebXRGamepadsModule.webidlWebTransport.webidlWebSerial.webidl、WebUSB.webidl、WebHID.webidl、Bluetooth.webidlFileSystemAccess.webidl、Clipboard.webidl、PictureInPicture.webidl、WebCodecs.webidl
同一目录下还划分了enabled/、disabled/、unavailable_option_primitive/等子目录,从目录结构看,unstable/用于存放规范仍在演进、尚未正式定稿的接口 IDL(详见下一节"不稳定 API")。
步骤 2:用[Throws]标注可抛错的函数
对规范中可能抛出异常的函数,在 WebIDL 中为其加上[Throws]注解。生成器读到该注解后,会把对应方法生成为返回Result的 Rust 函数,从而把 JS 异常安全地映射进 Rust 的错误处理流程。
步骤 3:进入 crate 目录
cd crates/web-sys步骤 4:运行生成器命令
cargo run --release --package wasm-bindgen-webidl -- webidls src/features ./Cargo.toml这条命令拆解如下:
| 参数 | 含义 |
|---|---|
--package wasm-bindgen-webidl | 指定要运行的 Cargo 包,即 crates/webidl 下的二进制程序 |
webidls | 第一个位置参数,即 WebIDL 输入目录(crates/web-sys/webidls) |
src/features | 第二个位置参数,即生成的 Rust 绑定代码输出目录 |
./Cargo.toml | 第三个位置参数,生成器会把新增接口对应的 Feature 自动写回该文件 |
对照 crates/webidl/src/main.rs,该命令执行的核心逻辑是:generate()解析webidls/下所有 WebIDL 文件,产出带#[cfg(feature = "…")]门控的 Rust 绑定到src/features/,随后update_cargo_toml_features()将新生成的 Feature(含依赖传递关系)追加进Cargo.toml的[features]段。README 明确提示此列表由wasm-bindgen-webidl自动生成,因此不要手改Feature 列表。
步骤 5:暂存生成的文件
git add .把所有生成文件(包括src/features/下的新绑定代码与更新后的Cargo.toml)加入版本控制。
步骤 6:更新 CHANGELOG
在 crates/web-sys/CHANGELOG.md 中按如下格式添加条目(PR 编号处填写你提交的 pull request 链接):
... ## Unreleased ### Added ... * Added <your addition> [#1234](https://github.com/wasm-bindgen/wasm-bindgen/pull/1234) # <- link to your PR测试佐证
新接口落地后,仓库内已有大量针对生成绑定的 wasm 测试可供参考,例如 crates/web-sys/tests/wasm/element.rs 中通过#[wasm_bindgen_test]直接调用Element的prefix()、local_name()、tag_name()、set_attribute()、toggle_attribute()等方法并断言结果,验证了绑定代码在真实浏览器/Node wasm 环境下的行为;crates/web-sys/tests/wasm/下还有blob.rs、console.rs、indexeddb.rs、history.rs、opfs.rs等覆盖各 API 域的测试文件,是评估一个接口绑定是否完整可用的现成样板。
六、不稳定 API:web_sys_unstable_apis开关
浏览器常常在规范尚未定稿时就先行实现部分 API,这类接口的 WebIDL 会随规范频繁变动。若 web-sys 直接按当前草案生成并发布,一旦草案修改,已发布的版本就"失效"了,而且 web-sys 会被迫频繁做出破坏性变更。解决方案(详见 guide/src/web-sys/unstable-apis.md)是把所有不稳定 API 用如下属性隐藏起来:
#[cfg(web_sys_unstable_apis)] pub struct Foo;这样,使用方必须**显式选择接受"较低稳定性保证"**才能看到这些 API;作为代价,这些 API不遵循 semver,WebIDL 一变化就可能被破坏。
启用方式有两种:
- 通过
RUSTFLAGS环境变量(推荐,最直接):
RUSTFLAGS=--cfg=web_sys_unstable_apis cargo run- 通过 Cargo 配置文件
./.cargo/config.toml:
[build] rustflags = ["--cfg=web_sys_unstable_apis"]对应地,crates/web-sys/Cargo.toml 在发布文档配置中也启用了该 cfg(rustdoc-args = ["--cfg=web_sys_unstable_apis"]、all-features = true),并在[lints.rust]中通过unexpected_cfgs的check-cfg声明了这一合法 cfg 标志;前述生成器命令的--next-unstable参数也与此机制配套。
七、生成管线的源码级透视
把 README 中的流程与仓库源码对照,可以更完整地理解 web-sys 的"生产线":
- 输入:WebIDL 规范文件,存放在 crates/web-sys/webidls/(
enabled/、disabled/、unstable/等子目录按接口的稳定性/可用性分类); - 处理:crates/webidl/src 下的
first_pass.rs(首遍解析)、traverse.rs(遍历 IDL 树)、generator.rs(生成 Rust 代码)、update_cargo_toml.rs(回写 Feature)共同完成从 WebIDL 到#[wasm_bindgen]绑定代码的转换; - 输出:生成结果写入
crates/web-sys/src/features/,Feature 列表自动同步进 crates/web-sys/Cargo.toml,并连带生成[Throws]→Result、继承链→Deref/AsRef、接口→独立 Feature 等约定产物。
也就是说,web-sys 约 700 余个 Feature(从AbortController一直到console、gpu_texture_usage等,覆盖 DOM、HTML 元素、SVG、WebGL、WebGPU、WebRTC、IndexedDB、传感器、XR 等领域)的最终形态,完全由这套 WebIDL→代码的自动化管线驱动,README 的六步流程正是这条管线的"人工操作手册"。
总结
- 定位:web-sys 是 wasm-bindgen 的 Web API 原始绑定层,覆盖面对标浏览器全部标准接口,不含 ECMAScript 内置对象(后者归 js-sys);
- 门控哲学:默认零内容,每个类型一个 Cargo Feature,方法可用性取决于
self类型与参数类型的 Feature 集合,祖先类型 Feature 自动传递; - 上手路径:
Cargo.toml加依赖 → 按 API 文档启用所需 Feature →#[wasm_bindgen]函数中直接调用; - 扩展路径:获取 WebIDL 放入 crates/web-sys/webidls/unstable/ → 标注
[Throws]→ 运行cargo run --release --package wasm-bindgen-webidl -- webidls src/features ./Cargo.toml→git add .→ 更新 CHANGELOG.md; - 稳定性边界:不成熟接口由
web_sys_unstable_apiscfg 隐藏,启用即放弃 semver 保证。
对 Web 平台 API 有完整、无封装、可直接编译为 wasm 的 Rust 绑定需求时,web-sys 是 wasm-bindgen 项目中最直接的答案;而当需求超出已收录范围,README 给出的六步流程则让每个开发者都能把缺失的接口补进这条自动化的 WebIDL 生成管线。
- 开发工具
【免费下载链接】wasm-bindgen
Facilitating high-level interactions between Wasm modules and JavaScript
相关推荐
深入解析 wasm-bindgen 的 web-sys crate:WebIDL 驱动的 Web API 绑定生成与 Cargo feature 机制
深入解析 wasm bindgen 的 web sys crate:WebIDL 驱动的 Web API 绑定生成与 Cargo feature 机制 web
开发工具wasm-bindgen 之 web-sys:可选原始类型参数(optional primitive)为何无法构建绑定
wasm bindgen 之 web sys:可选原始类型参数(optional primitive)为何无法构建绑定 导读 在 web sys 中,"可选原始
开发工具为 `web-sys` 扩展新的 Web API:从 WebIDL 到 Rust 绑定的完整贡献指南
为 web sys 扩展新的 Web API:从 WebIDL 到 Rust 绑定的完整贡献指南 web sys 是 wasm bindgen 生态中面向 We
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考