Agent skill

implementing-jsc-classes-zig

Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/implementing-jsc-classes-zig

SKILL.md

Bun's JavaScriptCore Class Bindings Generator

Bridge JavaScript and Zig through .classes.ts definitions and Zig implementations.

Architecture

  1. Zig Implementation (.zig files)
  2. JavaScript Interface Definition (.classes.ts files)
  3. Generated Code (C++/Zig files connecting them)

Class Definition (.classes.ts)

typescript
define({
  name: "TextDecoder",
  constructor: true,
  JSType: "object",
  finalize: true,
  proto: {
    decode: { args: 1 },
    encoding: { getter: true, cache: true },
    fatal: { getter: true },
  },
});

Options:

  • name: Class name
  • constructor: Has public constructor
  • JSType: "object", "function", etc.
  • finalize: Needs cleanup
  • proto: Properties/methods
  • cache: Cache property values via WriteBarrier

Zig Implementation

zig
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!JSValue return type for error handling
  • Use globalObject not ctx
  • deinit() for cleanup, finalize() called by GC
  • Update src/bun.js/bindings/generated_classes_list.zig

CallFrame Access

zig
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:

zig
// 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

zig
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

zig
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

  1. Define interface in .classes.ts:
typescript
define({
  name: "MyClass",
  constructor: true,
  finalize: true,
  proto: {
    myMethod: { args: 1 },
    myProperty: { getter: true, cache: true },
  },
});
  1. Implement in .zig:
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);
    }
};
  1. 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

Didn't find tool you were looking for?

Be as detailed as possible for better results