本文档面向 Bun 的维护者和贡献者,并描述了内部实现细节。
新的绑定生成器于 2024 年 12 月引入代码库,它会扫描 *.bind.ts 文件以查找函数和类定义,并生成胶水代码,用于 JavaScript 和原生代码之间的互操作。
目前存在其他实现类似 目的的代码生成器和系统。以下所有内容最终都将完全被 此生成器取代
- “类生成器”,用于转换 `*.classes.ts` 文件以生成自定义类。
- “JS2Native”,允许从 `src/js` 对原生代码进行临时调用。
在 Zig 中创建 JS 函数
给定一个实现简单函数的 Zig 文件,例如 `add`
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
return std.math.add(i32, a, b) catch {
// Binding functions can return `error.OutOfMemory` and `error.JSError`.
// Others like `error.Overflow` from `std.math.add` must be converted.
// Remember to be descriptive.
return global.throwPretty("Integer overflow while adding", .{});
};
}
const gen = bun.gen.math; // "math" being this file's basename
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;
然后使用 `.bind.ts` 函数描述 API 模式。绑定文件与 Zig 文件放在一起。
import { t, fn } from 'bindgen';
export const add = fn({
args: {
global: t.globalObject,
a: t.i32,
b: t.i32.default(1),
},
ret: t.i32,
});
此函数声明等同于
/**
* Throws if zero arguments are provided.
* Wraps out of range numbers using modulo.
*/
declare function add(a: number, b: number = 1): number;
代码生成器将提供 `bun.gen.math.jsAdd`,这是原生 函数实现。要将其传递给 JavaScript,请使用 bun.gen.math.createAddCallback(global)。`src/js/` 中的 JS 文件可以使用 $bindgenFn("math.bind.ts", "add") 来获取实现句柄。
字符串
用于接收字符串的类型是 t.DOMString、t.ByteString 和 t.USVString 之一。这些直接映射到它们的 WebIDL 同类项,并具有略微不同的转换逻辑。Bindgen 在所有情况下都将 BunString 传递给原生代码。
如有疑问,请使用 DOMString。
t.UTF8String 可用于替代 `t.DOMString`,但会调用 `bun.String.toUTF8`。原生回调函数会接收 `[]const u8` (WTF-8 数据) 并会在函数返回后释放。
WebIDL 规范的 TLDR(最长不适用)
- ByteString 只能包含有效的拉丁字符。不应假定 bun.String 已为 8 位格式,但可能性极高。
- USVString 不包含无效的代理对,即可以正确表示为 UTF-8 的文本。
- DOMString 是最宽松但也最推荐的策略。
函数变体
variants 可以指定多个变体(也称为重载)。
import { t, fn } from 'bindgen';
export const action = fn({
variants: [
{
args: {
a: t.i32,
},
ret: t.i32,
},
{
args: {
a: t.DOMString,
},
ret: t.DOMString,
},
]
});
在 Zig 中,每个变体根据模式定义的顺序获得一个数字。
fn action1(a: i32) i32 {
return a;
}
fn action2(a: bun.String) bun.String {
return a;
}
t.dictionary
dictionary 是 JavaScript 对象的定义,通常作为函数的输入。对于函数输出,通常更明智的做法是声明一个类类型来添加函数和解构。
枚举
要使用 WebIDL 的枚举 类型,请使用以下任一方法:
t.stringEnum:创建并代码生成一个新的枚举类型。t.zigEnum:基于代码库中现有的枚举派生一个 bindgen 类型。
在 `fmt.zig` / `bun:internal-for-testing` 中使用的 `stringEnum` 示例
export const Formatter = t.stringEnum(
"highlight-javascript",
"escape-powershell",
);
export const fmtString = fn({
args: {
global: t.globalObject,
code: t.UTF8String,
formatter: Formatter,
},
ret: t.DOMString,
});
WebIDL 强烈建议使用 kebab case(烤串式命名法)表示枚举值,以保持与现有 Web API 的一致性。
从 Zig 代码派生枚举
待办事项:zigEnum
t.oneOf
oneOf 是两个或多个类型之间的联合。在 Zig 中表示为 `union(enum)`。
待办事项
属性
有一组属性可以链接到 `t.*` 类型。所有类型都有
- `.required`,仅用于字典参数
- `.optional`,仅用于函数参数
.default(T)
当值是可选的时,它会被降低为 Zig 的可选类型。
根据类型,还有更多属性可用。请参阅 `auto-complete` 中的类型定义以获取更多详细信息。请注意,上述三者中只能应用一个,并且必须应用在最后。
整数属性
整数类型允许使用 `clamp` 或 `enforceRange` 来自定义溢出行为。
import { t, fn } from "bindgen";
export const add = fn({
args: {
global: t.globalObject,
// enforce in i32 range
a: t.i32.enforceRange(),
// clamp to u16 range
b: t.u16,
// enforce in arbitrary range, with a default if not provided
c: t.i32.enforceRange(0, 1000).default(5),
// clamp to arbitrary range, or null
d: t.u16.clamp(0, 10).optional,
},
ret: t.i32,
});
各种 Node.js 验证器函数,例如 `validateInteger`、`validateNumber` 等均可用。在实现 Node.js API 时使用这些函数,以使错误消息与 Node 的行为完全匹配。
与 `enforceRange`(取自 WebIDL)不同,`validate*` 函数对它们接受的输入更加严格。例如,Node 的数字验证器检查 `typeof value === 'number'`,而 WebIDL 使用 `ToNumber` 进行有损转换。
import { t, fn } from "bindgen";
export const add = fn({
args: {
global: t.globalObject,
// throw if not given a number
a: t.f64.validateNumber(),
// valid in i32 range
a: t.i32.validateInt32(),
// f64 within safe integer range
b: t.f64.validateInteger(),
// f64 in given range
c: t.f64.validateNumber(-10000, 10000),
},
ret: t.i32,
});
回调
待办事项
类
待办事项