Skip to content

NOTE

Questo documento è destinato ai maintainer e contributori di Bun e descrive dettagli di implementazione interni.

Il nuovo generatore di binding, introdotto nel codice a dicembre 2024, analizza i file *.bind.ts per trovare definizioni di funzioni e classi e genera codice glue per l'interop tra JavaScript e codice nativo.

Attualmente ci sono altri generatori di codice e sistemi che raggiungono scopi simili. Tutti questi verranno eventualmente eliminati completamente a favore di questo:

  • "Generatore di classi", che converte *.classes.ts per classi personalizzate.
  • "JS2Native", che consente chiamate ad-hoc da src/js al codice nativo.

Creazione di Funzioni JS in Zig

Dato un file che implementa una funzione semplice, come add:

zig
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
    return std.math.add(i32, a, b) catch {
        // Le funzioni di binding possono restituire `error.OutOfMemory` e `error.JSError`.
        // Altri come `error.Overflow` da `std.math.add` devono essere convertiti.
        // Ricorda di essere descrittivo.
        return global.throwPretty("Overflow intero durante l'addizione", .{});
    };
}

const gen = bun.gen.math; // "math" è il nome base di questo file

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

Poi descrivi lo schema API usando una funzione .bind.ts. Il file di binding va accanto al file 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,
});

Questa dichiarazione di funzione è equivalente a:

ts
/**
 * Lancia un errore se non vengono forniti argomenti.
 * Gestisce i numeri fuori intervallo usando il modulo.
 */
declare function add(a: number, b: number = 1): number;

Il generatore di codice fornirà bun.gen.math.jsAdd, che è l'implementazione della funzione nativa. Per passare a JavaScript, usa bun.gen.math.createAddCallback(global). I file JS in src/js/ possono usare $bindgenFn("math.bind.ts", "add") per ottenere un handle all'implementazione.

Stringhe

Il tipo per ricevere stringhe è uno tra t.DOMString, t.ByteString, e t.USVString. Questi mappano direttamente alle loro controparti WebIDL e hanno una logica di conversione leggermente diversa. Bindgen passerà BunString al codice nativo in tutti i casi.

In caso di dubbio, usa DOMString.

t.UTF8String può essere usato al posto di t.DOMString, ma chiamerà bun.String.toUTF8. Il callback nativo riceve []const u8 (dati WTF-8) passati al codice nativo, liberandoli dopo il ritorno della funzione.

TLDR dalle specifiche WebIDL:

  • ByteString può contenere solo caratteri latin1 validi. Non è sicuro assumere che bun.String sia già in formato a 8 bit, ma è estremamente probabile.
  • USVString non conterrà coppie surrogate non valide, ovvero testo che può essere rappresentato correttamente in UTF-8.
  • DOMString è la strategia più flessibile ma anche più raccomandata.

Varianti di Funzione

Un variants può specificare più varianti (note anche come overload).

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, ogni variante riceve un numero, basato sull'ordine in cui lo schema le definisce.

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

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

t.dictionary

Un dictionary è una definizione per un oggetto JavaScript, tipicamente come input di funzione. Per gli output di funzione, è di solito un'idea migliore dichiarare un tipo di classe per aggiungere funzioni e destructuring.

Enumerazioni

Per usare il tipo di enumerazione di WebIDL, usa uno dei seguenti:

  • t.stringEnum: Crea e genera una nuova enumerazione.
  • t.zigEnum: Deriva un tipo bindgen da un'enumerazione esistente nel codice.

Un esempio di stringEnum come usato in 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 incoraggia fortemente l'uso del kebab case per i valori di enumerazione, per essere coerenti con le API Web esistenti.

Derivazione di enumerazioni dal codice Zig

TODO: zigEnum

t.oneOf

Un oneOf è un'unione tra due o più tipi. È rappresentato da union(enum) in Zig.

TODO:

Attributi

C'è un insieme di attributi che possono essere concatenati ai tipi t.*. Su tutti i tipi ci sono:

  • .required, solo nei parametri dictionary
  • .optional, solo negli argomenti di funzione
  • .default(T)

Quando un valore è opzionale, viene abbassato a un opzionale Zig.

A seconda del tipo, ci sono più attributi disponibili. Vedi le definizioni dei tipi nel completamento automatico per maggiori dettagli. Nota che uno dei tre sopra può essere applicato solo uno alla volta e devono essere applicati alla fine.

Attributi Integer

I tipi integer consentono di personalizzare il comportamento dell'overflow con clamp o enforceRange:

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

export const add = fn({
  args: {
    global: t.globalObject,
    // applica nell'intervallo i32
    a: t.i32.enforceRange(),
    // limita all'intervallo u16
    b: t.u16,
    // applica in intervallo arbitrario, con un default se non fornito
    c: t.i32.enforceRange(0, 1000).default(5),
    // limita a intervallo arbitrario, o null
    d: t.u16.clamp(0, 10).optional,
  },
  ret: t.i32,
});

Varie funzioni di validazione di Node.js come validateInteger, validateNumber e altre sono disponibili. Usale quando implementi le API di Node.js, in modo che i messaggi di errore corrispondano 1:1 a ciò che farebbe Node.

A differenza di enforceRange, che è preso da WebIDL, le funzioni validate* sono molto più rigorose sugli input che accettano. Ad esempio, i validatori numerici di Node controllano typeof value === 'number', mentre WebIDL usa ToNumber per la conversione con perdita.

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

export const add = fn({
  args: {
    global: t.globalObject,
    // lancia errore se non viene fornito un numero
    a: t.f64.validateNumber(),
    // valido nell'intervallo i32
    a: t.i32.validateInt32(),
    // f64 nell'intervallo di interi sicuri
    b: t.f64.validateInteger(),
    // f64 nell'intervallo dato
    c: t.f64.validateNumber(-10000, 10000),
  },
  ret: t.i32,
});

Callback

TODO

Classi

TODO

Bun a cura di www.bunjs.com.cn