本文档面向 Bun 的维护者和贡献者,描述内部实现细节。
*.bind.ts 文件,以查找函数和类定义,并生成用于 JavaScript 与原生代码互操作的胶水代码。
还有其他代码生成器和系统可以实现类似功能;以下这些最终都会被逐步淘汰,转而采用此生成器:
- “Classes generator”,转换
*.classes.ts用于自定义类。 - “JS2Native”,允许从
src/js到原生代码的临时调用。
在 Rust 中创建 JS 函数
给定一个实现函数的文件,例如add:
src/jsc/bindgen_test.rs
.bind.ts 文件描述 API 模式。绑定文件位于 Rust 文件旁边。
src/jsc/bindgen_test.bind.ts
crate::r#gen::<basename> 访问(对于 bindgen_test.bind.ts,即 crate::r#gen::bindgen_test)。要构造一个包装原生实现的 JSFunction,请使用 generated::create_add_callback(global):
src/js/ 中的 JS 文件里,$bindgenFn("bindgen_test.bind.ts", "add") 会返回一个指向该实现的句柄。
导出的 bindgen 函数在 Rust 端会使用 snake_case 命名
(requiredAndOptionalArg → required_and_optional_arg),生成的回调构造函数也遵循相同的命名约定
(create_required_and_optional_arg_callback)。
字符串
要接收字符串,请使用t.DOMString、t.ByteString 或 t.USVString。这些类型直接对应于它们在 WebIDL 中的对应类型,并且具有略有不同的转换逻辑。在所有情况下,Bindgen 都会将 bun_core::String 传递给原生代码。
不确定时,使用 DOMString。
t.UTF8String 可以替代 t.DOMString,但会立即转换为 UTF-8。
原生回调会接收一个 &[u8] 切片(WTF-8 数据),并在函数返回后释放。
WebIDL 规范要点:
- ByteString 只能包含有效的 latin1 字符。不能假定
bun_core::String已经是 8 位格式,但这种情况极有可能。 - USVString 不包含无效的代理对,因此其文本可以正确地表示为 UTF-8。
- DOMString 的限制最少,同时也是最推荐的策略。
函数变体
variants 键声明函数的多个变体(也称为重载)。
t.dictionary
dictionary 描述一个 JavaScript 对象,通常用作函数输入。对于函数输出,建议使用类类型,以便添加方法并支持解构。
枚举
t.stringEnum 创建一个 WebIDL 枚举,并为其生成一个新的枚举类型。
以下是 fmt_jsc.bind.ts / bun:internal-for-testing 中 stringEnum 的示例:
#[repr(u8)] 枚举。Bindgen
在生成 C++ enum class 之前,会对 t.stringEnum 的值按字母顺序进行排序,因此判别值必须与生成的头文件顺序匹配,而不是 .bind.ts 中的声明顺序:
implNamespace
在 fn({...}) 上设置 implNamespace: "foo" 会将生成的调用路由到
crate::<basename>::foo::fn_name,而不是 crate::<basename>::fn_name。使用此选项可将相关的绑定实现归入一个子模块中。
t.oneOf
oneOf 是两个或多个类型的联合体。它表示为一个 Rust
enum,每种成员类型对应一个变体。
属性
属性可以链式附加到t.* 类型上。对于所有类型:
.required(仅用于字典参数).optional(仅用于函数参数).default(T)
.optional 时,它会被转换为 Rust 的 Option<T>:
整数属性
整数类型可以使用clamp 或 enforceRange 来自定义溢出行为:
validateInteger 和 validateNumber)也可用。在实现 Node.js API 时使用这些函数,可以使错误消息与 Node 完全一致。
与源自 WebIDL 的 enforceRange 不同,validate* 函数对其接受的输入限制更加严格。例如,Node 的数值验证器会检查 typeof value === 'number',而 WebIDL 使用 ToNumber 进行有损转换。