ブログ一覧に戻る
ガイド

BunにJSCクラスをC++で追加する方法:実装パターンガイド

Skill Gallery Team2026年2月1日8 分で読める

BunにJavaScriptクラスを追加したいけれど、JavaScriptCore(JSC)のC++ APIはボイラープレートが多く、何から書けばよいかわかりにくいと感じていませんか?

この記事では、implementing-jsc-classes-cppスキルが提供するC++バインディングのテンプレートと各パターンの使い方を解説します。

このスキルは何をしてくれるのか

JavaScriptCore上でC++を使ってJSクラスを実装する際の定型パターンを提供するリファレンスです。

  • JSCクラスの3層構造テンプレート: DestructibleObject、Prototype、Constructorの3層で構成されるクラス定義パターン
  • Iso Subspaceの設定方法: JSCのガベージコレクタ(GC)がC++フィールドを正しく管理するためのサブスペース登録
  • プロパティ定義テーブル: HashTableValueによるGetter、Setter、メソッドの宣言的な登録
  • Structure Caching: LazyClassStructureを使ったJSCオブジェクト構造のキャッシュ
  • Zig-C++間のFFI連携: extern "C"によるBunのZigコアとの接続

Bunへのコントリビューターや、JSC上でC++バインディングを書く開発者に向いています。

インストール方法

このスキルはBunリポジトリ内のガイドドキュメントです。スキル固有のインストールコマンドはありません。Bunリポジトリをクローンし、SKILL.mdを参照してください。

前提条件

  • Bunのソースコードリポジトリへのアクセス
  • LLVM 19(Bunのビルドに必須)
  • C++およびJavaScriptCoreの基礎知識

使い方

Iso Subspaceテンプレート

C++フィールドを持つJSCクラスを作成する場合、GCのメモリ管理のためIso Subspaceを定義します。

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); });
}

プロパティ定義テーブル

HashTableValueの配列でGetter、メソッドを宣言的に定義します。

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の実装

JSC_DEFINE_CUSTOM_GETTERマクロで型チェック付きの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()));
}

Structure CachingとZig連携

LazyClassStructureでStructureをキャッシュし、extern "C"でZigに公開します。

// ZigGlobalObject.h
JSC::LazyClassStructure m_JSFooClassStructure;

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

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

知っておくべき注意点

subspaceの登録漏れ

C++フィールドを持つクラスは、DOMClientIsoSubspaces.hとDOMIsoSubspaces.hの両方にsubspaceを追加する必要があります。片方だけでは正しく動作しません。

root.hのインクルード

C++ファイルの先頭に#include "root.h"がないとコンパイルエラーになります。Bun固有の要件です。

3層構造は必須ではない

publicコンストラクタが不要なら、PrototypeとClassのみで構成できます。

LLVM 19が必須

BunのビルドにはLLVM 19が必要です。バージョン不一致はメモリ割り当て失敗の原因になります。

自動コード生成の検討

Bunにはgenerate-classes.tsによる自動コード生成パイプラインがあり、多くの場合は.classes.tsでの宣言的定義が推奨されます。手動実装が必要かどうかを事前に判断してください。

まとめ

implementing-jsc-classes-cppスキルは、BunにおけるJSCクラスのC++実装パターンをまとめたリファレンスです。Iso Subspace、プロパティ定義、Structure Caching、Zig連携のテンプレートを提供しています。

まずgenerate-classes.tsで対応できるか確認し、手動実装が必要な場合に活用してください。

関連リンク

bunjavascriptcorecppzigruntimeoven-sh

関連記事