Boaは、Rustだけで書かれた組み込み向けのJavaScriptエンジンです。Rustのプログラムに「ユーザーが書いたJavaScriptを実行させる口」を付けたいときの選択肢になりますが、公式リポジトリが自らを experimental と表記しているとおり、V8やQuickJSの置き換えとして選ぶものではありません。最新版 boa_engine 0.21.1 を前提に、公式が公開している準拠度データとベンチマークスコアの実数値、導入手順、そして採用を見送るべき条件を整理します。
まとめ
- 最新版は boa_engine 0.21.1(2026-03-29公開、crates.io)。機能追加は0.21(2025-10-21)が最後で、以降はパッチのみ。ライセンスは Unlicense OR MIT。
- ECMAScript準拠度は公式データで 53,125件中51,073件がpass、570件がfail(boa-dev/data、2026-07-25更新)。同じ式での準拠度は96.1%。
- 速度はJITを持たないインタプリタ相当。公式ベンチマークのスコアは Boa 215に対しQuickJS 1,227、V8(JIT無効)2,522。計測対象8エンジン中で最下位。
- 向いているのは「Rustプロセスの内側で、外部由来のJavaScriptを限定的に走らせる」用途。Ethereum実行環境のJSトレーサ(revm-inspectors)が採用例。
- 無限ループや深い再帰は
context.runtime_limits_mut()でRust側から強制的に打ち切れる。この打ち切りはJavaScript側のtry...catchでは捕捉できない。
Boa(boa_engine)の現在地
Boaは boa-dev コミュニティ(公式サイトは boajs.dev)がGitHubで開発しているオープンソースのJavaScriptエンジンで、レキサ・パーサ・インタプリタのすべてをRustで実装しています。crates.ioに boa_engine が登録されたのは2022年3月15日、累計ダウンロードは約394万、直近90日で約127万です。ライセンスは Unlicense OR MIT のデュアルで、商用利用・改変・再配布に実質的な制約はありません。
リリース頻度は年1〜3回です。2024年は0.18.0(03-07)、0.19.0(07-11)、0.19.1(09-12)、0.20.0(12-05)と4本出た一方、2025年は0.21.0(10-21)の1本にとどまりました。0.x系が続いていることからも分かるとおりAPIは安定版に達しておらず、READMEの冒頭も「experimental JavaScript lexer, parser and interpreter」と書いています。検証や社内ツールで使う分には実用的ですが、長期運用するプロダクトへ組み込むならマイナー版ごとの破壊的変更を前提に見積もる必要があります。Rust側の基礎知識はRust入門|環境構築から所有権・基本文法、受託開発での採用判断までで確認できます。
エンジンとランタイムの境界
Boaが提供するのはJavaScriptの構文解析と実行だけです。ファイル読み書き、HTTP、タイマーといった機能はECMAScript仕様の外側にあるため、boa_engine単体には入っていません。これらは別クレートの boa_runtime が担い、0.21で fetch、setTimeout、queueMicrotask が実装されました。それでもNode.jsのようなモジュールエコシステムやファイルシステムAPIは揃っていないため、npmパッケージをそのまま動かす想定では使えません。
この切り分けはV8とNode.jsの関係と同じで、エンジンは実行機構、ランタイムはその上に載るAPI群という関係です。エンジン側の実行単位という考え方はV8 Isolateとは?仕組み・Contextとの違い・サーバーレスでの活用を徹底解説で整理しています。
クレート構成と非推奨になったクレート
Boaは機能ごとにクレートが分かれています。公式が活発に保守すると明記しているのは boa_engine、boa_runtime(Web API相当)、boa_cli(REPL)、boa_gc(GC)、boa_parser、boa_ast、boa_interner、boa_string、boa_icu_provider、そしてポインタにusize型のタグを付与するユーティリティ tag_ptr の10クレートです。通常はboa_engineだけを依存に入れ、必要に応じて追加します。
注意したいのは、README上で Boa(大文字始まりの旧クレート)と boa_unicode が非推奨として明記されている点です。2023年前後の記事には Boa = "0.x" という依存指定が残っていることがありますが、いま新規に書くなら boa_engine を指定してください。
Test262の実カウントによるECMAScript準拠度
BoaはECMAScript公式テストスイート(Test262)の結果を boa-dev/data リポジトリへ継続公開しており、mainブランチの集計は毎日更新されています。2026-07-25更新時点の生データは次のとおりです。
| 区分 | 件数 |
|---|---|
| テスト総数 | 53,125 |
| pass | 51,073 |
| ignored(対象外) | 1,482 |
| panic | 0 |
| fail | 570 |
panicは53,125件中0件です。ただしこれはTest262という仕様適合テストの範囲でハーネスが異常終了しなかったという意味であり、敵対的な入力に対する堅牢性の保証ではありません。信頼できない入力を扱うなら、後述の runtime_limits_mut() による上限設定と併用してください。
公式が採っている準拠度の式は「pass ÷ 総数」です。v0.21タグ時点のデータ(50,595件中47,635件pass)を当てはめると94.15%となり、リリースノートの公表値94.12%とほぼ一致します。同じ式を2026-07-25の生データへ当てると 51,073 ÷ 53,125 = 96.1% です。0.20時点の公表値は89.92%だったので、1年半で約6ポイント上がった計算になります。Temporal提案はTemporal関連テストで約97%に到達したとリリースノートに記載があります。数値は集計日ごとに動くため、判断材料にするときは公式のconformanceページで最新を確認してください。
速度の実測位置|公式ベンチマークスコアの比較
「Rust製だから軽量で高速」という説明が国内の解説記事に散見されますが、公式が公開している実測値はそれを支持していません。boa-dev/data のベンチマーク結果(V8ベンチマークスイート、スコアが高いほど速い)から各エンジンの最新スコアを抜き出すと次のようになります。
| エンジン | スコア | 実装言語 | 最終計測日 |
|---|---|---|---|
| V8(JIT無効) | 2,522 | C++ | 2026-07-25 |
| Porffor | 2,308 | TypeScript | 2026-07-24 |
| QuickJS | 1,227 | C | 2026-07-25 |
| libjs | 840 | C++ | 2026-01-16 |
| SpiderMonkey(JIT無効) | 780 | C++ | 2026-07-25 |
| Kiesel | 391 | Zig | 2026-07-25 |
| Duktape | 381 | C | 2026-07-25 |
| Boa | 215 | Rust | 2026-07-25 |
V8とSpiderMonkeyはJITを無効にした条件での計測です。PorfforはAOTコンパイル方式で前提が異なり、libjsは2026-01-16以降の計測が止まっています。それらを差し引いても、Boaは計測対象8エンジン中で最下位でした。V8の約12分の1、同じ組み込み用途で競合するQuickJSの約6分の1、より小規模なDuktapeより下という位置です。BoaにJITコンパイラは無く、0.21でスタックベースの仮想マシンからレジスタベースの仮想マシンへ移行した段階にあります。
改善は速い。同じ計測系列で2024-07-07時点のスコアは81だったので、2年で約2.7倍になりました。0.21で入った JsValue のNaNボクシング表現(値をメモリ上でコンパクトに保持する手法)とレジスタVM化がこの伸びを支えています。ただし伸び率が高いことと、いま速いことは別です。スループットが要件に入るワークロードでBoaを選ぶ判断は、現時点では成立しません。
boa_engineの導入とRustからのJavaScript実行
Cargo.tomlへの追加と最低Rustバージョン
導入は依存を1行足すだけです。Cコンパイラもビルドスクリプトも要らず、cargoだけで完結します。
[dependencies]
boa_engine = "0.21.1"
0.21系は Rust 1.88.0以上、edition 2024 を要求します。CIやDockerイメージのツールチェーンが古いとここで止まるため、先に更新してください。Rust本体の最近の変更点はRust 1.97.0とは|v0シンボルマングリング既定化とCargo警告制御の変更点【2026年7月】、依存管理の基本操作はCargo(Rust)とは?インストールから使い方・主要コマンドまで完全ガイドにまとめています。
最小構成でのスクリプト評価
実行の起点は Context です。公式READMEが載せている最小例をそのまま示します。
use boa_engine::{Context, Source, JsResult};
fn main() -> JsResult<()> {
let js_code = r#"
let two = 1 + 1;
let definitely_not_four = two + "2";
definitely_not_four
"#;
let mut context = Context::default();
let result = context.eval(Source::from_bytes(js_code))?;
println!("{}", result.display());
Ok(())
}
この例が返すのは数値の4ではなく文字列の “22” です。JavaScriptの暗黙型変換がそのまま再現されている、つまりエンジンとして仕様どおりに振る舞っていることの確認になります。
Rust関数のJavaScriptへの公開
実用上の本題は、ホスト側の機能をJavaScriptへ渡すことです。register_global_callable にクロージャを登録すると、グローバル関数としてスクリプトから呼び出せます。
use boa_engine::{
Context, JsResult, JsString, JsValue, Source,
native_function::NativeFunction,
};
fn main() -> JsResult<()> {
let mut context = Context::default();
let budget = 255;
context.register_global_callable(
JsString::from("remaining_budget"),
0,
NativeFunction::from_copy_closure(move |_, _, _| {
Ok(JsValue::new(budget))
}),
)?;
let value = context.eval(Source::from_bytes("remaining_budget()"))?;
println!("{}", value.display());
Ok(())
}
クロージャがRust側の変数をムーブで取り込めるため、設定値やハンドルをJavaScriptへ渡す形で組み立てられます。オブジェクトやクラスを公開する場合は JsObject と js_string! マクロを使い、値の生成は0.21で追加されたマクロ群で簡潔に書けます。
wasm32ターゲット向けのビルド設定
Boa自体がWASMプレイグラウンドとして公開されているとおり、wasm32ターゲットでも動きます。ただし wasm32-unknown-unknown に限り、乱数取得のバックエンド指定が必要です。js フィーチャを有効にしたうえで、プロジェクト直下の .cargo/config.toml に次を書きます。
[target.wasm32-unknown-unknown]
rustflags = '--cfg getrandom_backend="wasm_js"'
WASIやEmscripten向けのターゲットでは自動的に処理されるため、この指定は不要です。
ループ回数・再帰深さの上限設定|Rust側からの強制打ち切り
外部から受け取ったJavaScriptを動かす前提なら、この機能が採用理由になり得ます。Boaは runtime_limits_mut() でループの反復回数と再帰の深さに上限を設定でき、超えた時点で実行を打ち切ります。
use boa_engine::{Context, Source};
fn main() {
let mut context = Context::default();
context.runtime_limits_mut().set_loop_iteration_limit(10_000);
context.runtime_limits_mut().set_recursion_limit(64);
// 無限ループはエラーとして返り、ホスト側のプロセスは止まらない
let result = context.eval(Source::from_bytes("while (true) { }"));
assert!(result.is_err());
}
この打ち切りエラーはJavaScript側の try...catch では捕捉できず、Rust側でしか受け取れません。公式のサンプルコードにも「This error cannot be caught in JavaScript, it can only be caught in Rust code.」と明記されています。スクリプト側に例外を握りつぶして居座られる余地がないため、信頼できない入力に対する防御として機能します。V8を埋め込んで同等の制御を実装する場合は Isolate::TerminateExecution と割り込みコールバックを自前で組む必要があり、その手間との差は小さくありません。
上限に達したかどうかは eval の戻り値で判定します。打ち切ったコンテキストを再利用するかは用途次第ですが、コンテキストごと破棄する設計にしておくほうが状態の持ち越しを考えずに済みます。エンジンが確保するメモリの考え方はRustのスタックとヒープ|値がヒープへ脱出する条件とメモリ領域の使い分けが参考になります。
Boaを選ぶ条件と、選ぶべきでない条件
採用が成立する条件
判断軸は速度ではなく「何をRustプロセスの中で完結させたいか」です。第一に、すでにRustで書かれたアプリケーションへ、ユーザー定義のルールやプラグインをJavaScriptで書かせたい場合。第二に、依存にC/C++のビルド環境を持ち込みたくない場合で、QuickJSやDuktapeはC実装のためCコンパイラを含むビルド環境が要りますが、Boaは純Rustなので cargo build だけで通ります。第三に、wasm32へそのまま載せたい場合。第四に、前章の実行上限のようにホスト側から資源を強制的に制御したい場合です。
revm-inspectorsのJSトレーサという採用例
Ethereum実行環境のインスペクタ実装である paradigmxyz/revm-inspectors は、JSトレーサ機能の依存として boa_engine 0.21 と boa_gc を指定しています。ユーザーが書いたトレース用スクリプトをRustプロセス内で評価するという、Boaの想定用途そのものです。ただし js-tracer はオプトインのフィーチャで既定では有効になっていないため、同クレートのダウンロード数がそのままBoaの利用規模を示すわけではありません。規模の目安になるのは boa_engine 自身の直近90日ダウンロード約127万件と、crates.io上で boa_engine に依存する82クレートという数字です。その中には、YouTubeの署名解読スクリプトを評価する rusty_ytdl のように「外部サイトのJSをRustから走らせる」用途が並びます。
見送るべき条件
最優先の失格条件はスループットです。QuickJSの約6分の1という実測差は、チューニングで埋まる幅ではありません。処理量が要件に入るなら、この時点で選択肢から外してください。次に、npmパッケージやNode.js互換APIをそのまま動かしたい場合。boa_runtimeが持つのは fetch、setTimeout、queueMicrotask 程度で、モジュール解決の仕組みは自前で用意することになります。ブラウザ互換の検証基盤のように仕様の完全準拠が前提となる用途も、fail 570件が残る現状では条件を満たしません。API安定性については、0.x系である以上マイナー版更新のたびに改修が発生する前提で見積もってください。
0.18〜0.20のコードを0.21へ移すときの詰まりどころ
ネット上の日本語解説は0.18〜0.19時点のものが多く、そのままでは0.21でビルドが通りません。版ごとの前提条件は次のとおりです。
| 版 | 公開日 | 最低Rustバージョン | edition |
|---|---|---|---|
| 0.18.0 | 2024-03-07 | 1.74.0 | 2021 |
| 0.19.0 | 2024-07-11 | 1.79.0 | 2021 |
| 0.19.1 | 2024-09-12 | 1.79.0 | 2021 |
| 0.20.0 | 2024-12-05 | 1.82.0 | 2021 |
| 0.21.0 | 2025-10-21 | 1.88.0 | 2024 |
| 0.21.1 | 2026-03-29 | 1.88.0 | 2024 |
影響が大きいのは0.21での非同期まわりの刷新です。JobQueue が作り直され、ModuleLoader が非同期化されたため、独自のジョブキューやモジュールローダーを実装していたコードは書き直しになります。tokioやsmolと組み合わせる例は公式リポジトリの examples(tokio_event_loop.rs、smol_event_loop.rs、module_fetch_async.rs)が0.21対応済みなので、そちらを起点にするのが早道です。
内部実装ではJsValueのNaNボクシング化とレジスタVMへの移行が入っていますが、通常のAPI利用で意識する必要はありません。バイトコードや命令列に直接触れていた場合のみ影響します。組み込み関数では Atomics.waitAsync、Setの新メソッド群、TypedArray/DataView/MathのFloat16対応、Error.isError、Math.sumPrecise、Array.fromAsync が0.21で追加されました。0.20以前を前提に「未対応」と書かれた記事の記述は、この時点で古くなっています。
よくある質問
Boa JSとboa_engineは同じものですか
プロジェクト名がBoa(Boa JSと呼ばれることもあります)、Rustから使うときのクレート名が boa_engine です。なお大文字始まりの旧クレート Boa は非推奨になっているため、依存には boa_engine を指定してください。
BoaはNode.jsの代わりに使えますか
使えません。Boaが実装しているのはECMAScript本体であり、ファイルシステムやHTTPサーバといったNode.jsのAPI、npmのパッケージ解決は含まれません。boa_runtimeで fetch、setTimeout、queueMicrotask が使えるようになった段階です。Rustアプリの中に小さなスクリプト実行環境を作る用途と考えてください。
Boaの最新バージョンはどれですか
2026年7月時点の最新は boa_engine 0.21.1(2026-03-29公開)です。0.21.1はWASM互換性の修正とリリースワークフローの修正を含むパッチリリースで、機能追加は0.21.0(2025-10-21)が最後になります。
BoaとQuickJS(rquickjs)はどちらを選ぶべきですか
速度と成熟度ならQuickJS側になる。公式ベンチマークのスコアはQuickJSが1,227に対しBoaが215で、rquickjsの直近90日ダウンロードは約130万件とboa_engineの約127万件で拮抗しています。一方でrquickjsはQuickJS本体をCでビルドするためCコンパイラを含む環境が必要で、クロスコンパイルやwasm32での取り回しは純Rustのboa_engineが有利。ビルド環境の制約が厳しいならBoa、性能要件があるならQuickJSという分け方が実務的です。
BoaでES Modules(import)は使えますか
使えます。ただしモジュールの解決はエンジンの外側の仕事なので、どこからソースを読むかを ModuleLoader として自前で用意します。0.21でこのローダーが非同期化されたため、HTTP経由でモジュールを取得する構成も組めます。実装例は公式リポジトリの modulehandler.rs と module_fetch_async.rs にあります。手早く試すだけなら boa_cli にモジュール実行のオプションがあり、-m でファイルをモジュールとして扱い、-r で解決の基点となるルートパスを指定できます。