Bun

Bindgen

此文件適用於 Bun 的維護者和貢獻者,並描述了內部的實作細節。

新的綁定產生器於 2024 年 12 月引入程式碼庫,掃描 *.bind.ts 以尋找函數和類別定義,並產生膠合程式碼以 在 JavaScript 和原生程式碼之間互通。

目前還有其他程式碼產生器和系統可以達成類似的 目的。以下所有項目最終都將完全淘汰,轉而採用 這個

  • 「類別產生器」,轉換 *.classes.ts 以用於自訂類別。
  • 「JS2Native」,允許從 src/js 到原生程式碼的特設呼叫。

在 Zig 中建立 JS 函數

假設有一個檔案實作了簡單的函數,例如 add

src/bun.js/math.zig
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 檔案旁邊。

src/bun.js/math.bind.ts
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.DOMStringt.ByteStringt.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 可以指定多個變體 (也稱為多載)。

src/bun.js/math.bind.ts
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。

根據類型,還有更多屬性可用。請參閱自動完成中的類型定義以瞭解更多詳細資訊。請注意,以上三個屬性中只能套用一個,且必須在最後套用。

整數屬性

整數類型允許使用 clampenforceRange 自訂溢位行為。

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 驗證器函式 (例如 validateIntegervalidateNumber 等) 可用。在實作 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,
});

回呼

待辦事項

類別

待辦事項