Skip to content

NOTE

Ce document est destiné aux mainteneurs et contributeurs de Bun, et décrit des détails d'implémentation interne.

Le nouveau générateur de liaisons, introduit dans la base de code en décembre 2024, analyse les fichiers *.bind.ts pour trouver les définitions de fonctions et de classes, et génère du code glue pour l'interopérabilité entre JavaScript et le code natif.

Il existe actuellement d'autres générateurs de code et systèmes qui atteignent des objectifs similaires. Tous seront éventuellement complètement remplacés par celui-ci :

  • "Générateur de classes", convertissant *.classes.ts pour des classes personnalisées.
  • "JS2Native", permettant des appels ad-hoc depuis src/js vers le code natif.

Créer des fonctions JS en Zig

Étant donné un fichier implémentant une fonction simple, comme add :

zig
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
    return std.math.add(i32, a, b) catch {
        // Les fonctions de liaison peuvent retourner `error.OutOfMemory` et `error.JSError`.
        // D'autres comme `error.Overflow` de `std.math.add` doivent être converties.
        // N'oubliez pas d'être descriptif.
        return global.throwPretty("Integer overflow while adding", .{});
    };
}

const gen = bun.gen.math; // "math" étant le nom de base de ce fichier

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

Ensuite, décrivez le schéma d'API en utilisant une fonction .bind.ts. Le fichier de liaison se trouve à côté du fichier Zig.

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

Cette déclaration de fonction est équivalente à :

ts
/**
 * Lance une erreur si aucun argument n'est fourni.
 * Enveloppe les nombres hors plage en utilisant modulo.
 */
declare function add(a: number, b: number = 1): number;

Le générateur de code fournira bun.gen.math.jsAdd, qui est l'implémentation de la fonction native. Pour passer à JavaScript, utilisez bun.gen.math.createAddCallback(global). Les fichiers JS dans src/js/ peuvent utiliser $bindgenFn("math.bind.ts", "add") pour obtenir un handle vers l'implémentation.

Chaînes

Le type pour recevoir des chaînes est l'un des suivants : t.DOMString, t.ByteString, et t.USVString. Ceux-ci correspondent directement à leurs équivalents WebIDL, et ont une logique de conversion légèrement différente. Bindgen passera BunString au code natif dans tous les cas.

En cas de doute, utilisez DOMString.

t.UTF8String peut être utilisé à la place de t.DOMString, mais appellera bun.String.toUTF8. Le callback natif reçoit []const u8 (données WTF-8) passé au code natif, en le libérant après le retour de la fonction.

Résumés de la spécification WebIDL :

  • ByteString ne peut contenir que des caractères latin1 valides. Il n'est pas sûr de supposer que bun.String est déjà au format 8 bits, mais c'est extrêmement probable.
  • USVString ne contiendra pas de paires de substitution invalides, c'est-à-dire du texte qui peut être représenté correctement en UTF-8.
  • DOMString est la stratégie la plus souple mais aussi la plus recommandée.

Variantes de fonctions

Un variants peut spécifier plusieurs variantes (également appelées surcharges).

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

En Zig, chaque variante reçoit un numéro, basé sur l'ordre dans lequel le schéma les définit.

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

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

t.dictionary

Un dictionary est une définition pour un objet JavaScript, généralement comme entrées de fonction. Pour les sorties de fonction, c'est généralement une idée plus intelligente de déclarer un type de classe pour ajouter des fonctions et du destructuring.

Énumérations

Pour utiliser le type énumération de WebIDL, utilisez soit :

  • t.stringEnum : Crée et génère un nouveau type d'énumération.
  • t.zigEnum : Dérive un type bindgen d'une énumération existante dans la base de code.

Un exemple de stringEnum tel qu'utilisé dans fmt.zig / bun:internal-for-testing

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 encourage fortement l'utilisation de la casse kebab pour les valeurs d'énumération, pour être cohérent avec les API Web existantes.

Dériver des énumérations depuis le code Zig

TODO : zigEnum

t.oneOf

Un oneOf est une union entre deux types ou plus. Il est représenté par union(enum) en Zig.

TODO :

Attributs

Il y a un ensemble d'attributs qui peuvent être chaînés aux types t.*. Sur tous les types, il y a :

  • .required, dans les paramètres de dictionnaire uniquement
  • .optional, dans les arguments de fonction uniquement
  • .default(T)

Lorsqu'une valeur est optionnelle, elle est réduite à une optionnelle Zig.

Selon le type, il y a plus d'attributs disponibles. Consultez les définitions de type dans l'auto-complétion pour plus de détails. Notez que l'un des trois ci-dessus ne peut être appliqué, et ils doivent être appliqués à la fin.

Attributs d'entiers

Les types d'entiers permettent de personnaliser le comportement de débordement avec clamp ou enforceRange

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

export const add = fn({
  args: {
    global: t.globalObject,
    // appliquer dans la plage i32
    a: t.i32.enforceRange(),
    // clamp à la plage u16
    b: t.u16,
    // appliquer dans une plage arbitraire, avec une valeur par défaut si non fournie
    c: t.i32.enforceRange(0, 1000).default(5),
    // clamp à une plage arbitraire, ou null
    d: t.u16.clamp(0, 10).optional,
  },
  ret: t.i32,
});

Diverses fonctions de validation Node.js telles que validateInteger, validateNumber, et plus sont disponibles. Utilisez-les lors de l'implémentation des API Node.js, afin que les messages d'erreur correspondent 1:1 à ce que Node ferait.

Contrairement à enforceRange, qui est tiré de WebIDL, les fonctions validate* sont beaucoup plus strictes sur l'entrée qu'elles acceptent. Par exemple, le validateur numérique de Node vérifie typeof value === 'number', tandis que WebIDL utilise ToNumber pour une conversion avec perte.

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

export const add = fn({
  args: {
    global: t.globalObject,
    // lancer une erreur si ce n'est pas un nombre
    a: t.f64.validateNumber(),
    // valide dans la plage i32
    a: t.i32.validateInt32(),
    // f64 dans la plage d'entiers sûrs
    b: t.f64.validateInteger(),
    // f64 dans la plage donnée
    c: t.f64.validateNumber(-10000, 10000),
  },
  ret: t.i32,
});

Callbacks

TODO

Classes

TODO

Bun édité par www.bunjs.com.cn