Bun's JavaScriptCore Class Bindings Generator
Bridge JavaScript and Zig through .classes.ts definitions and Zig implementations.
Architecture
- Zig Implementation (.zig files)
- JavaScript Interface Definition (.classes.ts files)
- Generated Code (C++/Zig files connecting them)
Class Definition (.classes.ts)
define({ name: "TextDecoder", constructor: true, JSType: "object", finalize: true, proto: { decode: { args: 1 }, encoding: { getter: true, cache: true }, fatal: { getter: true }, }, });
Options:
name: Class nameconstructor: Has public constructorJSType: "object", "function", etc.finalize: Needs cleanupproto: Properties/methodscache: Cache property values via WriteBarrier
Zig Implementation
pub const TextDecoder = struct { pub const js = JSC.Codegen.JSTextDecoder; pub const toJS = js.toJS; pub const fromJS = js.fromJS; pub const fromJSDirect = js.fromJSDirect; encoding: []const u8, fatal: bool, pub fn constructor( globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame, ) bun.JSError!*TextDecoder { return bun.new(TextDecoder, .{ .encoding = "utf-8", .fatal = false }); } pub fn decode( this: *TextDecoder, globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame, ) bun.JSError!JSC.JSValue { const args = callFrame.arguments(); if (args.len < 1 or args.ptr[0].isUndefinedOrNull()) { return globalObject.throw("Input cannot be null", .{}); } return JSC.JSValue.jsString(globalObject, "result"); } pub fn getEncoding(this: *TextDecoder, globalObject: *JSGlobalObject) JSC.JSValue { return JSC.JSValue.createStringFromUTF8(globalObject, this.encoding); } fn deinit(this: *TextDecoder) void { // Release resources } pub fn finalize(this: *TextDecoder) void { this.deinit(); bun.destroy(this); } };
Key patterns:
- Use
bun.JSError!JSValuereturn type for error handling - Use
globalObjectnotctx deinit()for cleanup,finalize()called by GC- Update
src/bun.js/bindings/generated_classes_list.zig
CallFrame Access
const args = callFrame.arguments(); const first_arg = args.ptr[0]; // Access as slice const argCount = args.len; const thisValue = callFrame.thisValue();
Property Caching
For cache: true properties, generated accessors:
// Get cached value pub fn encodingGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { const result = TextDecoderPrototype__encodingGetCachedValue(thisValue); if (result == .zero) return null; return result; } // Set cached value pub fn encodingSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { TextDecoderPrototype__encodingSetCachedValue(thisValue, globalObject, value); }
Error Handling
pub fn method(this: *MyClass, globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { const args = callFrame.arguments(); if (args.len < 1) { return globalObject.throw("Missing required argument", .{}); } return JSC.JSValue.jsString(globalObject, "Success!"); }
Memory Management
pub fn deinit(this: *TextDecoder) void { this._encoding.deref(); if (this.buffer) |buffer| { bun.default_allocator.free(buffer); } } pub fn finalize(this: *TextDecoder) void { JSC.markBinding(@src()); this.deinit(); bun.default_allocator.destroy(this); }
Creating a New Binding
- Define interface in
.classes.ts:
define({ name: "MyClass", constructor: true, finalize: true, proto: { myMethod: { args: 1 }, myProperty: { getter: true, cache: true }, }, });
- Implement in
.zig:
pub const MyClass = struct { pub const js = JSC.Codegen.JSMyClass; pub const toJS = js.toJS; pub const fromJS = js.fromJS; value: []const u8, pub const new = bun.TrivialNew(@This()); pub fn constructor(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!*MyClass { return MyClass.new(.{ .value = "" }); } pub fn myMethod(this: *MyClass, globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { return JSC.JSValue.jsUndefined(); } pub fn getMyProperty(this: *MyClass, globalObject: *JSGlobalObject) JSC.JSValue { return JSC.JSValue.jsString(globalObject, this.value); } pub fn deinit(this: *MyClass) void {} pub fn finalize(this: *MyClass) void { this.deinit(); bun.destroy(this); } };
- Add to
src/bun.js/bindings/generated_classes_list.zig
Generated Components
- C++ Classes:
JSMyClass,JSMyClassPrototype,JSMyClassConstructor - Method Bindings:
MyClassPrototype__myMethodCallback - Property Accessors:
MyClassPrototype__myPropertyGetterWrap - Zig Bindings: External function declarations, cached value accessors