NOTE
Este documento está dirigido a mantenedores y contribuidores de Bun, y describe detalles de implementación interna.El nuevo generador de enlaces, introducido en la base de código en diciembre de 2024, escanea archivos *.bind.ts para encontrar definiciones de funciones y clases, y genera código de conexión para interoperar entre JavaScript y código nativo.
Actualmente existen otros generadores de código y sistemas que logran propósitos similares. Todos los siguientes eventualmente se eliminarán por completo en favor de este:
- "Generador de clases", convirtiendo
*.classes.tspara clases personalizadas. - "JS2Native", permitiendo llamadas ad-hoc desde
src/jsa código nativo.
Crear funciones JS en Zig
Dado un archivo que implementa una función simple, como add
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
return std.math.add(i32, a, b) catch {
// Las funciones de enlace pueden devolver `error.OutOfMemory` y `error.JSError`.
// Otros como `error.Overflow` de `std.math.add` deben convertirse.
// Recuerda ser descriptivo.
return global.throwPretty("Desbordamiento de entero al sumar", .{});
};
}
const gen = bun.gen.math; // "math" siendo el nombre base de este archivo
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;Luego describe el esquema de API usando una función .bind.ts. El archivo de enlace va junto al archivo 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,
});Esta declaración de función es equivalente a:
/**
* Lanza un error si no se proporcionan argumentos.
* Envuelve números fuera de rango usando módulo.
*/
declare function add(a: number, b: number = 1): number;El generador de código proporcionará bun.gen.math.jsAdd, que es la implementación de la función nativa. Para pasar a JavaScript, usa bun.gen.math.createAddCallback(global). Los archivos JS en src/js/ pueden usar $bindgenFn("math.bind.ts", "add") para obtener un manejador a la implementación.
Cadenas
El tipo para recibir cadenas es uno de t.DOMString, t.ByteString, y t.USVString. Estos se mapean directamente a sus contrapartes WebIDL, y tienen lógica de conversión ligeramente diferente. Bindgen pasará BunString al código nativo en todos los casos.
Cuando tengas dudas, usa DOMString.
t.UTF8String puede usarse en lugar de t.DOMString, pero llamará a bun.String.toUTF8. La devolución de llamada nativa recibe []const u8 (datos WTF-8) pasados al código nativo, liberándolo después de que la función retorna.
Resúmenes rápidos de la especificación WebIDL:
- ByteString solo puede contener caracteres latin1 válidos. No es seguro asumir que bun.String ya está en formato de 8 bits, pero es extremadamente probable.
- USVString no contendrá pares sustitutos inválidos, también conocido como texto que puede representarse correctamente en UTF-8.
- DOMString es la estrategia más flexible pero también más recomendada.
Variantes de función
Un variants puede especificar múltiples variantes (también conocidas como sobrecargas).
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, cada variante obtiene un número, basado en el orden en que el esquema las define.
fn action1(a: i32) i32 {
return a;
}
fn action2(a: bun.String) bun.String {
return a;
}t.dictionary
Un dictionary es una definición para un objeto de JavaScript, típicamente como entradas de función. Para salidas de función, generalmente es una idea más inteligente declarar un tipo de clase para agregar funciones y desestructuración.
Enumeraciones
Para usar el tipo de enumeración de WebIDL, usa uno de los siguientes:
t.stringEnum: Crea y genera un nuevo tipo de enumeración.t.zigEnum: Deriva un tipo bindgen de una enumeración existente en la base de código.
Un ejemplo de stringEnum como se usa en 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 recomienda encarecidamente usar caso kebab para los valores de enumeración, para ser consistente con las API web existentes.
Derivar enumeraciones desde código Zig
TODO: zigEnum
t.oneOf
Un oneOf es una unión entre dos o más tipos. Se representa como union(enum) en Zig.
TODO:
Atributos
Hay un conjunto de atributos que se pueden encadenar a tipos t.*. En todos los tipos existen:
.required, solo en parámetros de diccionario.optional, solo en argumentos de función.default(T)
Cuando un valor es opcional, se reduce a un opcional de Zig.
Dependiendo del tipo, hay más atributos disponibles. Consulta las definiciones de tipo en el autocompletado para más detalles. Ten en cuenta que solo se puede aplicar uno de los tres anteriores, y deben aplicarse al final.
Atributos de entero
Los tipos de entero permiten personalizar el comportamiento de desbordamiento con clamp o enforceRange
import { t, fn } from "bindgen";
export const add = fn({
args: {
global: t.globalObject,
// forzar en rango i32
a: t.i32.enforceRange(),
// limitar al rango u16
b: t.u16,
// forzar en rango arbitrario, con un valor predeterminado si no se proporciona
c: t.i32.enforceRange(0, 1000).default(5),
// limitar al rango arbitrario, o null
d: t.u16.clamp(0, 10).optional,
},
ret: t.i32,
});Varias funciones validadoras de Node.js como validateInteger, validateNumber, y más están disponibles. Úsalas al implementar APIs de Node.js, para que los mensajes de error coincidan 1:1 con lo que haría Node.
A diferencia de enforceRange, que se toma de WebIDL, las funciones validate* son mucho más estrictas en la entrada que aceptan. Por ejemplo, el validador numérico de Node verifica typeof value === 'number', mientras que WebIDL usa ToNumber para conversión con pérdida.
import { t, fn } from "bindgen";
export const add = fn({
args: {
global: t.globalObject,
// lanzar error si no se proporciona un número
a: t.f64.validateNumber(),
// válido en rango i32
a: t.i32.validateInt32(),
// f64 dentro del rango de enteros seguros
b: t.f64.validateInteger(),
// f64 en el rango dado
c: t.f64.validateNumber(-10000, 10000),
},
ret: t.i32,
});Devoluciones de llamada
TODO
Clases
TODO