NOTE
このドキュメントは Bun のメンテナーと貢献者向けであり、内部の実装詳細について説明しています。2024 年 12 月にコードベースに導入された新しいバインディングジェネレーターは、*.bind.ts をスキャンして関数とクラスの定義を見つけ、JavaScript とネイティブコード間の相互運用のためのグルーコードを生成します。
現在、同様の目的を達成する他のコードジェネレーターとシステムがいくつか存在します。以下はすべて、最終的にこの 1 つに完全に置き換えられる予定です:
- 「クラスジェネレーター」、カスタムクラス用に
*.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 {
// バインディング関数は `error.OutOfMemory` および `error.JSError` を返すことができます。
// `std.math.add` からの `error.Overflow` のようなその他は変換する必要があります。
// 説明的であることを忘れないでください。
return global.throwPretty("Integer overflow while adding", .{});
};
}
const gen = bun.gen.math; // "math" はこのファイルのベース名
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,
});この関数宣言は以下と同等です:
/**
* 引数が 0 個提供された場合、スローします。
* 範囲外の数値をモジュロを使用してラップします。
*/
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 では、スキーマが定義する順序に基づいて、各バリアントに番号が付けられます。
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 は、既存の Web API と一貫性を保つために、列挙型の値にケバブケースを使用することを強く推奨しています。
Zig コードから列挙型を派生させる
TODO: zigEnum
t.oneOf
oneOf は 2 つ以上の型の間の共用体です。Zig では union(enum) として表されます。
TODO:
属性
t.* 型にチェーンできる属性のセットがあります。すべての型に以下があります:
.required、辞書パラメーターのみ.optional、関数引数のみ.default(T)
値がオプションの場合、Zig オプショナルに低下します。
型に応じて、より多くの属性が利用可能です。詳細は、オートコンプリートの型定義を参照してください。上記 3 つのうちの 1 つのみを適用でき、最後に適用する必要があることに注意してください。
整数属性
整数型は、clamp または enforceRange を使用してオーバーフロー動作をカスタマイズできます。
import { t, fn } from "bindgen";
export const add = fn({
args: {
global: t.globalObject,
// i32 範囲で強制
a: t.i32.enforceRange(),
// u16 範囲にクランプ
b: t.u16,
// 任意の範囲で強制、提供されない場合はデフォルト
c: t.i32.enforceRange(0, 1000).default(5),
// 任意の範囲にクランプ、または null
d: t.u16.clamp(0, 10).optional,
},
ret: t.i32,
});validateInteger、validateNumber など、さまざまな Node.js 検証関数が利用可能です。Node.js API を実装する際は、エラーメッセージが Node の実行内容と 1 対 1 で一致するように、これらを使用してください。
enforceRange(WebIDL から取得)とは異なり、validate* 関数は受け入れる入力に対してはるかに厳格です。例えば、Node の数値検証関数は typeof value === 'number' をチェックしますが、WebIDL は損失のある変換のために ToNumber を使用します。
import { t, fn } from "bindgen";
export const add = fn({
args: {
global: t.globalObject,
// 数値が与えられない場合にスロー
a: t.f64.validateNumber(),
// i32 範囲で有効
a: t.i32.validateInt32(),
// 安全な整数範囲内の f64
b: t.f64.validateInteger(),
// 指定された範囲内の f64
c: t.f64.validateNumber(-10000, 10000),
},
ret: t.i32,
});コールバック
TODO
クラス
TODO