oven-sh/bun

implementing-jsc-classes-cpp

Implements JavaScript classes in C++ using JavaScriptCore.

Voir la source

Mode d’emploi

  1. Copiez la commande et examinez le paquet avant de l’installer dans votre environnement agent.
  2. Consultez le document original pour les outils, identifiants et environnements requis.
  3. Décrivez les entrées et le livrable souhaité ; comparez la sortie aux sources.
npx skills add https://github.com/oven-sh/bun --skill implementing-jsc-classes-cpp

Avant de commencer

  • L’installation n’inclut ni abonnement au modèle, ni quota API, ni accès aux fichiers.
  • Étoiles et installations reflètent l’activité, pas un test indépendant de qualité ou de sécurité.
Document Skill original

Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.

Implementing JavaScript Classes in C++

Class Structure

For publicly accessible Constructor and Prototype, create 3 classes:

  1. `class Foo : public JSC::DestructibleObject` - if C++ fields exist; otherwise use JSC::constructEmptyObject with putDirectOffset
  2. `class FooPrototype : public JSC::JSNonFinalObject`
  3. `class FooConstructor : public JSC::InternalFunction`

No public constructor? Only Prototype and class needed.

Iso Subspaces

Classes with C++ fields need subspaces in:

  • src/jsc/bindings/webcore/DOMClientIsoSubspaces.h
  • src/jsc/bindings/webcore/DOMIsoSubspaces.h
cpp
template<typename MyClassT, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) {
    if constexpr (mode == JSC::SubspaceAccess::Concurrently)
        return nullptr;
    return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
        vm, BUN_SUBSPACE_SLOTS(m_clientSubspaceForMyClassT, m_subspaceForMyClassT));
}

subspaceForImpl only reads the slots inline. The subspaces themselves are created by subspaceForImplSlow in src/jsc/bindings/BunClientData.cpp, which is the only place that constructs an IsoSubspace (test/internal/source-lints/iso-subspace-creation.test.ts checks this).

Property Definitions

cpp
static JSC_DECLARE_HOST_FUNCTION(jsFooProtoFuncMethod);
static JSC_DECLARE_CUSTOM_GETTER(jsFooGetter_property);

static const HashTableValue JSFooPrototypeTableValues[] = {
    { "property"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsFooGetter_property, 0 } },
    { "method"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFooProtoFuncMethod, 1 } },
};

Prototype Class

cpp
class JSFooPrototype final : public JSC::JSNonFinalObject {
public:
    using Base = JSC::JSNonFinalObject;
    static constexpr unsigned StructureFlags = Base::StructureFlags;

    static JSFooPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) {
        JSFooPrototype* prototype = new (NotNull, allocateCell<JSFooPrototype>(vm)) JSFooPrototype(vm, structure);
        prototype->finishCreation(vm);
        return prototype;
    }

    template<typename, JSC::SubspaceAccess>
    static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return &vm.plainObjectSpace(); }

    DECLARE_INFO;

    static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) {
        auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
        structure->setMayBePrototype(true);
        return structure;
    }

private:
    JSFooPrototype(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) {}
    void finishCreation(JSC::VM& vm);
};

void JSFooPrototype::finishCreation(VM& vm) {
    Base::finishCreation(vm);
    reifyStaticProperties(vm, JSFoo::info(), JSFooPrototypeTableValues, *this);
    JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}

Getter/Setter/Function Definitions

cpp
// Getter
JSC_DEFINE_CUSTOM_GETTER(jsFooGetter_prop, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) {
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);
    JSFoo* thisObject = jsDynamicCast<JSFoo*>(JSValue::decode(thisValue));
    if (UNLIKELY(!thisObject)) {
        Bun::throwThisTypeError(*globalObject, scope, "JSFoo"_s, "prop"_s);
        return {};
    }
    return JSValue::encode(jsBoolean(thisObject->value()));
}

// Function
JSC_DEFINE_HOST_FUNCTION(jsFooProtoFuncMethod, (JSGlobalObject* globalObject, CallFrame* callFrame)) {
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);
    auto* thisObject = jsDynamicCast<JSFoo*>(callFrame->thisValue());
    if (UNLIKELY(!thisObject)) {
        Bun::throwThisTypeError(*globalObject, scope, "Foo"_s, "method"_s);
        return {};
    }
    return JSValue::encode(thisObject->doSomething(vm, globalObject));
}

Constructor Class

cpp
class JSFooConstructor final : public JSC::InternalFunction {
public:
    using Base = JSC::InternalFunction;
    static constexpr unsigned StructureFlags = Base::StructureFlags;

    static JSFooConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) {
        JSFooConstructor* constructor = new (NotNull, JSC::allocateCell<JSFooConstructor>(vm)) JSFooConstructor(vm, structure);
        constructor->finishCreation(vm, prototype);
        return constructor;
    }

    DECLARE_INFO;

    template<typename CellType, JSC::SubspaceAccess>
    static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return &vm.internalFunctionSpace(); }

    static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) {
        return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
    }

private:
    JSFooConstructor(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure, callFoo, constructFoo) {}

    void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) {
        Base::finishCreation(vm, 0, "Foo"_s);
        putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
    }
};

Structure Caching

Add to ZigGlobalObject.h:

cpp
JSC::LazyClassStructure m_JSFooClassStructure;

Initialize in ZigGlobalObject.cpp:

cpp
m_JSFooClassStructure.initLater([](LazyClassStructure::Initializer& init) {
    Bun::initJSFooClassStructure(init);
});

Visit in visitChildrenImpl:

cpp
m_JSFooClassStructure.visit(visitor);

Expose to Zig

cpp
extern "C" JSC::EncodedJSValue Bun__JSFooConstructor(Zig::GlobalObject* globalObject) {
    return JSValue::encode(globalObject->m_JSFooClassStructure.constructor(globalObject));
}

extern "C" EncodedJSValue Bun__Foo__toJS(Zig::GlobalObject* globalObject, Foo* foo) {
    auto* structure = globalObject->m_JSFooClassStructure.get(globalObject);
    return JSValue::encode(JSFoo::create(globalObject->vm(), structure, globalObject, WTFMove(foo)));
}

Include #include "root.h" at the top of C++ files.

du même dépôt

Autres Skills

Tous les Skills