How to Implement JSC Classes in C++ for Bun Runtime
Want to add a new JavaScript class inside the Bun runtime but find JavaScriptCore's C++ API overwhelming with boilerplate? You need to define class structures, set up Iso Subspaces for garbage collection, declare property tables, and wire everything up with Bun's Zig core through FFI -- all in the correct order.
The implementing-jsc-classes-cpp skill provides a comprehensive reference of the boilerplate patterns required when writing C++ bindings for JSC classes inside Bun. This article walks through the templates it offers and how to apply each pattern.
What This Skill Does
The implementing-jsc-classes-cpp skill provides production-ready templates for implementing JavaScript classes in C++ on top of JavaScriptCore. It covers the following areas:
- JSC class three-layer structure: Templates for the DestructibleObject (instance), Prototype, and Constructor layers that make up a JSC class definition
- Iso Subspace configuration: Registration templates that enable JSC's garbage collector to correctly manage C++ fields
- Property definition tables (HashTableValue): Declarative registration of getters, setters, and methods via table definitions
- Structure Caching with LazyClassStructure: Performance optimization by caching JSC object structures
- Zig-C++ FFI integration: Patterns using
extern "C"to expose JSC classes to Bun's Zig core
This skill is aimed at developers who want to contribute to the Bun runtime or those working at a low level who need to write C++ bindings for the JavaScriptCore engine.
Installation
This skill is a guide document within the Bun repository. You reference it after cloning Bun's source code and setting up the development environment.
Prerequisites
- Access to the Bun source code repository
- LLVM 19 (required for building Bun)
- Familiarity with C++ and JavaScriptCore fundamentals
No dedicated install command is provided for this skill. Clone the Bun repository and refer to the SKILL.md file directly.
Usage
Iso Subspace Template
When creating a JSC class with C++ fields, define an Iso Subspace so the garbage collector can manage memory correctly.
// Iso Subspace template
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,
[](auto& spaces) { return spaces.m_clientSubspaceForMyClassT.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForMyClassT = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForMyClassT.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForMyClassT = std::forward<decltype(space)>(space); });
}
Property Definition Table
Define getters, setters, and methods declaratively as an array of HashTableValue entries.
// Property definition table
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 } },
};
Getter Implementation
Implement custom getters using the JSC_DEFINE_CUSTOM_GETTER macro, which includes type checking and error handling.
// Getter implementation pattern
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()));
}
Structure Caching and Zig Integration
Use LazyClassStructure to cache JSC Structures and expose them to Zig.
// Structure Caching (add to ZigGlobalObject.h)
JSC::LazyClassStructure m_JSFooClassStructure;
// Initialize in ZigGlobalObject.cpp
m_JSFooClassStructure.initLater([](LazyClassStructure::Initializer& init) {
Bun::initJSFooClassStructure(init);
});
// Expose to Zig
extern "C" JSC::EncodedJSValue Bun__JSFooConstructor(Zig::GlobalObject* globalObject) {
return JSValue::encode(globalObject->m_JSFooClassStructure.constructor(globalObject));
}
Things to Keep in Mind
Subspace Registration in Two Files
Classes with C++ fields require subspace entries in both DOMClientIsoSubspaces.h and DOMIsoSubspaces.h. Missing either file will cause issues.
The root.h Include
Every C++ file must include #include "root.h" at the top, or you will encounter compile errors. This is a Bun-specific requirement.
Three-Layer Structure Is Not Always Required
If no public constructor is needed, you can use only the Prototype and Class layers. Not every JSC class needs the full three-layer setup.
LLVM 19 Is Required
Bun requires LLVM 19 to build. Using a different version can lead to memory allocation failures at runtime.
Automatic Code Generation Pipeline
This skill covers manual C++ implementation, but Bun also has an automatic code generation pipeline via generate-classes.ts. In many cases, declarative definitions through .classes.ts files are the recommended approach. Determine whether manual implementation is necessary before using these templates.
Summary
The implementing-jsc-classes-cpp skill is a systematic reference for C++ JSC class implementation patterns in the Bun runtime. It provides copy-ready templates for Iso Subspaces, property definitions, Structure Caching, and Zig integration.
If you are considering contributing to Bun, first check whether the automatic code generation pipeline (generate-classes.ts) covers your use case. Use the manual templates from this skill when automatic generation is not sufficient.
For full template details, visit the skill page.