此文件適用於 Bun 的維護者和貢獻者,並描述了內部的實作細節。
新的綁定產生器於 2024 年 12 月引入程式碼庫,掃描 *.bind.ts
以尋找函數和類別定義,並產生膠合程式碼以 在 JavaScript 和原生程式碼之間互通。
目前還有其他程式碼產生器和系統可以達成類似的 目的。以下所有項目最終都將完全淘汰,轉而採用 這個
- 「類別產生器」,轉換
*.classes.ts
以用於自訂類別。 - 「JS2Native」,允許從
src/js
到原生程式碼的特設呼叫。
在 Zig 中建立 JS 函數
假設有一個檔案實作了簡單的函數,例如 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("root").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 規範的重點摘要
- ByteString 只能包含有效的 latin1 字元。假設 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 中,每個變體都會根據 schema 定義的順序獲得一個數字。
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 類型。
stringEnum
的範例,如 fmt.zig
/ bun:internal-for-testing
中所使用
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
,僅在 dictionary 參數中.optional
,僅在函式引數中.default(T)
當值為 optional 時,它會被降級為 Zig optional。
根據類型,還有更多屬性可用。請參閱自動完成中的類型定義以瞭解更多詳細資訊。請注意,以上三個屬性中只能套用一個,且必須在最後套用。
整數屬性
整數類型允許使用 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.js 的行為完全一致。
與取自 WebIDL 的 enforceRange
不同,validate*
函式對它們接受的輸入更加嚴格。例如,Node.js 的數值驗證器會檢查 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,
});
回呼
待辦事項
類別
待辦事項