Skip to content

NOTE

Dieses Dokument ist für Maintainer und Contributors von Bun und beschreibt interne Implementierungsdetails.

Der neue Bindings-Generator, der im Dezember 2024 in die Codebasis eingeführt wurde, scannt nach *.bind.ts, um Funktions- und Klassendefinitionen zu finden, und generiert Glue-Code zur Interoperabilität zwischen JavaScript und nativem Code.

Es gibt derzeit andere Code-Generatoren und Systeme, die ähnliche Zwecke erfüllen. Die folgenden werden jedoch alle schließlich zugunsten dieses einen vollständig ausgemustert:

  • "Klassen-Generator", der *.classes.ts für benutzerdefinierte Klassen konvertiert.
  • "JS2Native", das Ad-hoc-Aufrufe von src/js zu nativem Code ermöglicht.

JS-Funktionen in Zig erstellen

Gegeben eine Datei, die eine einfache Funktion implementiert, wie add:

zig
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
    return std.math.add(i32, a, b) catch {
        // Binding-Funktionen können error.OutOfMemory und error.JSError zurückgeben.
        // Andere wie error.Overflow von std.math.add müssen konvertiert werden.
        // Denken Sie daran, beschreibend zu sein.
        return global.throwPretty("Integer overflow while adding", .{});
    };
}

const gen = bun.gen.math; // "math" ist der Basisname dieser Datei

const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;

Beschreiben Sie dann das API-Schema mit einer .bind.ts Funktion. Die Binding-Datei befindet sich neben der Zig-Datei.

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,
});

Diese Funktionsdeklaration ist äquivalent zu:

ts
/**
 * Wirft, wenn null Argumente bereitgestellt werden.
 * Wickelt Zahlen außerhalb des Bereichs mit Modulo.
 */
declare function add(a: number, b: number = 1): number;

Der Code-Generator stellt bun.gen.math.jsAdd bereit, was die native Funktionsimplementierung ist. Um an JavaScript zu übergeben, verwenden Sie bun.gen.math.createAddCallback(global). JS-Dateien in src/js/ können $bindgenFn("math.bind.ts", "add") verwenden, um einen Handle zur Implementierung zu erhalten.

Strings

Der Typ zum Empfangen von Strings ist einer von t.DOMString, t.ByteString, und t.USVString. Diese entsprechen direkt ihren WebIDL-Gegenstücken und haben leicht unterschiedliche Konvertierungslogik. Bindgen wird in allen Fällen BunString an nativen Code übergeben.

Verwenden Sie im Zweifelsfall DOMString.

t.UTF8String kann anstelle von t.DOMString verwendet werden, ruft jedoch bun.String.toUTF8 auf. Der native Callback erhält []const u8 (WTF-8-Daten), die an nativen Code übergeben werden, und gibt sie nach Rückkehr der Funktion frei.

TLDRs von der WebIDL-Spezifikation:

  • ByteString kann nur gültige latin1-Zeichen enthalten. Es ist nicht sicher anzunehmen, dass bun.String bereits im 8-Bit-Format ist, aber es ist äußerst wahrscheinlich.
  • USVString enthält keine ungültigen Surrogatpaare, also Text, der korrekt in UTF-8 dargestellt werden kann.
  • DOMString ist die lockerste, aber auch am meisten empfohlene Strategie.

Funktionsvarianten

Ein variants kann mehrere Varianten (auch bekannt als Überladungen) spezifizieren.

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,
    },
  ],
});

In Zig erhält jede Variante eine Nummer, basierend auf der Reihenfolge, in der das Schema definiert ist.

zig
fn action1(a: i32) i32 {
  return a;
}

fn action2(a: bun.String) bun.String {
  return a;
}

t.dictionary

Ein dictionary ist eine Definition für ein JavaScript-Objekt, typischerweise als Funktionseingaben. Für Funktionsausgaben ist es normalerweise eine klügere Idee, einen Klassentyp zu deklarieren, um Funktionen und Destrukturierung hinzuzufügen.

Enumerationen

Um WebIDs Enumeration Typ zu verwenden, verwenden Sie entweder:

  • t.stringEnum: Erstellen und codegennen Sie einen neuen Enum-Typ.
  • t.zigEnum: Leiten Sie einen Bindgen-Typ von einem vorhandenen Enum in der Codebasis ab.

Ein Beispiel für stringEnum wie in fmt.zig / bun:internal-for-testing verwendet:

ts
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 empfiehlt dringend die Verwendung von Kebab-Case für Enumerationswerte, um mit bestehenden Web-APIs konsistent zu sein.

Enums von Zig-Code ableiten

TODO: zigEnum

t.oneOf

Ein oneOf ist eine Vereinigung zwischen zwei oder mehr Typen. Es wird durch union(enum) in Zig dargestellt.

TODO:

Attribute

Es gibt einen Satz von Attributen, die an t.* Typen angehängt werden können. Bei allen Typen gibt es:

  • .required, nur in Dictionary-Parametern
  • .optional, nur in Funktionsargumenten
  • .default(T)

Wenn ein Wert optional ist, wird er zu einem Zig-Optional reduziert.

Je nach Typ stehen weitere Attribute zur Verfügung. Weitere Details finden Sie in den Typdefinitionen in der Autovervollständigung. Beachten Sie, dass nur eines der oben genannten drei angewendet werden kann und sie am Ende angewendet werden müssen.

Integer-Attribute

Integer-Typen ermöglichen die Anpassung des Overflow-Verhaltens mit clamp oder enforceRange

ts
import { t, fn } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // im i32-Bereich erzwingen
    a: t.i32.enforceRange(),
    // auf u16-Bereich klemmen
    b: t.u16,
    // im beliebigen Bereich erzwingen, mit einem Standardwert, falls nicht angegeben
    c: t.i32.enforceRange(0, 1000).default(5),
    // auf beliebigen Bereich klemmen, oder null
    d: t.u16.clamp(0, 10).optional,
  },
  ret: t.i32,
});

Verschiedene Node.js-Validator-Funktionen wie validateInteger, validateNumber und mehr sind verfügbar. Verwenden Sie diese bei der Implementierung von Node.js-APIs, damit die Fehlermeldungen 1:1 mit dem übereinstimmen, was Node tun würde.

Im Gegensatz zu enforceRange, das von WebIDL übernommen ist, sind validate*-Funktionen bei den akzeptierten Eingaben viel strenger. Zum Beispiel überprüft Nodes numerischer Validator typeof value === 'number', während WebIDL ToNumber für verlustbehaftete Konvertierung verwendet.

ts
import { t, fn } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // werfen, wenn keine Zahl gegeben ist
    a: t.f64.validateNumber(),
    // gültig im i32-Bereich
    a: t.i32.validateInt32(),
    // f64 im Bereich sicherer Integer
    b: t.f64.validateInteger(),
    // f64 im angegebenen Bereich
    c: t.f64.validateNumber(-10000, 10000),
  },
  ret: t.i32,
});

Callbacks

TODO

Klassen

TODO

Bun von www.bunjs.com.cn bearbeitet