はじめに
「アプリにJavaScriptを組み込みたいけれど、V8は大きすぎる…」と感じたことはありませんか?Node.jsやブラウザで使われるV8は強力ですが、バイナリサイズが数十MBに達し、組み込み用途にはヘビー級です。IoTデバイスやプラグインシステム、サンドボックス実行など「小さくJavaScriptを動かしたい」場面では、もっと軽い選択肢が欲しくなります。
そこで登場するのがQuickJSです。FFmpegやQEMUの作者として知られるFabrice Bellard氏が開発した、小型で組み込み可能なJavaScriptエンジンで、Hello Worldプログラムがわずか約367KiBという驚異的なコンパクトさを誇ります。この記事では、QuickJSの特徴からインストール、実践的な活用方法までを解説します。
とはいえ、読むより触った方が早いと思います。ブラウザ上でWASM版のquickjs-emscriptenを使い、getQuickJS()とevalCode()で実際にJavaScriptコードを評価できるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
QuickJSとは
QuickJSは、C言語で書かれた軽量なJavaScriptエンジンです。外部依存が一切なく、数個のCファイルだけで構成されているため、あらゆるCプログラムに簡単に組み込めます。最新版(2026年6月リリース)ではES2025仕様にほぼ完全対応しており、モジュール、非同期ジェネレータ、Proxy、BigIntなどのモダンな機能も利用できます。
主な特徴
- 超軽量 - Hello Worldの実行ファイルが約367KiB。V8やSpiderMonkeyと比べて桁違いに小さく、組み込み機器でも動作します
- 高速起動 - 起動オーバーヘッドがほぼゼロ。ECMAScript公式テストスイート全体を約2分で実行できる処理性能を持ち、最新版は前バージョン比で42%高速化されています
- ES2025対応 - test262(ECMAScript準拠テスト)でほぼ100%のパス率を達成。最新のJavaScript構文がそのまま動きます
- JSを実行ファイルにコンパイル可能 - 付属の
qjscコンパイラで、JavaScriptを依存なしのスタンドアロン実行ファイルに変換できます - 参照カウント方式のGC - メモリ使用量が予測しやすく、省メモリ環境に適しています
- MITライセンス - 商用利用も安心のライセンスです
インストール
ソースからビルド(Linux / macOS)
QuickJSは依存がないため、ビルドも簡単です。
# ソースコードを取得
git clone https://github.com/bellard/quickjs.git
cd quickjs
# ビルドしてインストール
make
sudo make install
これでqjs(インタプリタ)とqjsc(コンパイラ)が使えるようになります。
パッケージマネージャーを使う場合
# macOS (Homebrew)
brew install quickjs
# Ubuntu / Debian
sudo apt install quickjs
Node.jsから使う場合
WebAssembly版のラッパーであるquickjs-emscriptenを使うと、Node.jsやブラウザからQuickJSをサンドボックスとして利用できます。
npm install quickjs-emscripten
QuickJSのサンプルを動かす
QuickJSはC言語製のエンジンなので、そのままではブラウザ上で動かせません。ただしWASMにコンパイルされたquickjs-emscriptenを使えば、ブラウザだけでQuickJSのサンドボックス評価を体験できます。中心となるAPIは2つだけです。getQuickJS()でWASMモジュールを非同期に読み込み、得られたQuickJSオブジェクトのnewContext()で評価用のコンテキスト(vm)を作成します。あとはvm.evalCode(コード文字列)に評価したいJavaScriptを渡すだけです。
import { getQuickJS } from "quickjs-emscripten";
async function run(code: string) {
const QuickJS = await getQuickJS();
const vm = QuickJS.newContext();
const result = vm.evalCode(code);
if (result.error) {
const detail = vm.dump(result.error);
result.error.dispose();
throw new Error(JSON.stringify(detail));
}
const value = vm.dump(result.value);
result.value.dispose();
vm.dispose();
return value;
}
下のサンプルは、入力欄に書いたJavaScriptコードをvm.evalCode()でその場で評価するプレイグラウンドです。QuickJSのWASMモジュール読み込みに少し時間がかかるので、「準備完了」の表示が出てから操作してください。初期値の[1, 2, 3].map(n => n * n).reduce((a, b) => a + b)は正しく評価できて14が表示されますが、たとえば末尾の)を消したり、存在しない変数名(undefinedVarなど)に書き換えたりすると、result.error側の分岐に入りエラー内容が赤字で表示されます。ホストのファイルシステムやネットワークには一切アクセスできないので、どんなコードを入力しても安全に試せます。
evalCode()が返す結果オブジェクトは、成功時はresult.value、失敗時はresult.errorのどちらかにハンドルが入る形になっています。vm.dump()でJavaScriptの値に変換したら、使い終わったハンドルはdispose()で明示的に解放するのがQuickJSのお作法です。この後片付けを忘れるとメモリリークにつながるので、evalCode()を呼んだら必ずセットで覚えておくとよいでしょう。
基本的な使い方
qjsでスクリプトを実行する
まずは定番のHello Worldです。
// hello.js
console.log("Hello, QuickJS!");
qjs hello.js
# => Hello, QuickJS!
ワンライナーの評価や対話モードも利用できます。
# 式を直接評価
qjs -e "console.log(1 + 2)"
# 対話モード(REPL)
qjs -i
ES Modulesを使う
-mオプション、または拡張子.mjsでESモジュールとして実行できます。
// math.mjs
export function add(a, b) {
return a + b;
}
// main.mjs
import { add } from "./math.mjs";
console.log(add(2, 3)); // => 5
qjs main.mjs
stdモジュールとosモジュール
QuickJSには、ファイルI/Oやシステム操作のためのstd・osモジュールが標準搭載されています。
// file-io.mjs
import * as std from "std";
import * as os from "os";
// ファイルの読み込み
const content = std.loadFile("hello.js");
console.log(content);
// 環境変数の取得
console.log(std.getenv("HOME"));
// ディレクトリの一覧取得
const [files, err] = os.readdir(".");
if (err === 0) {
files.forEach((f) => console.log(f));
}
// タイマーも使えます
os.setTimeout(() => {
console.log("1秒経過しました");
}, 1000);
qjs file-io.mjs
実践的なユースケース
1. qjscでJavaScriptを実行ファイルにする
QuickJS最大の魅力のひとつが、JavaScriptをスタンドアロンの実行ファイルにコンパイルできる点です。Node.jsのインストールされていない環境にも、単一バイナリとして配布できます。
// greet.js
const name = scriptArgs[1] ?? "World";
console.log(`Hello, ${name}!`);
# コンパイル
qjsc -o greet greet.js
# 実行(Node.js不要!)
./greet QuickJS
# => Hello, QuickJS!
-fno-evalや-fno-regexpなどのオプションで不要な言語機能を無効化すれば、バイナリをさらに小さくできます。
2. CアプリケーションにJavaScriptを組み込む
QuickJSの本領は、Cプログラムへの組み込みです。プラグイン機構や設定スクリプトとして、アプリにJavaScriptを実行させられます。
// embed.c
#include "quickjs.h"
#include <stdio.h>
int main(void) {
// ランタイム(ヒープ管理)とコンテキスト(グローバル環境)を作成
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
const char *script = "const x = 21; x * 2;";
JSValue result = JS_Eval(ctx, script, strlen(script),
"<input>", JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(result)) {
printf("エラーが発生しました\n");
} else {
int32_t value;
JS_ToInt32(ctx, &value, result);
printf("結果: %d\n", value); // => 結果: 42
}
// 後片付け
JS_FreeValue(ctx, result);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
gcc embed.c -o embed -lquickjs -lm -lpthread
./embed
JS_NewCFunction()を使えばC関数をJavaScript側に公開でき、双方向の連携も可能です。
3. Node.jsでサンドボックスとして使う
quickjs-emscriptenを使うと、信頼できないコードを隔離された環境で安全に評価できます。ユーザー定義の数式やルールを実行するSaaSなどで便利です。
// sandbox.ts
import { getQuickJS } from "quickjs-emscripten";
async function main() {
const QuickJS = await getQuickJS();
const vm = QuickJS.newContext();
// サンドボックス内で計算式を評価(ホスト環境には一切アクセスできません)
const result = vm.evalCode("[1, 2, 3].map((n) => n ** 2).reduce((a, b) => a + b)");
if (result.error) {
console.error("評価エラー:", vm.dump(result.error));
result.error.dispose();
} else {
console.log("結果:", vm.dump(result.value)); // => 結果: 14
result.value.dispose();
}
vm.dispose();
}
main();
QuickJSのコンテキストはホストのファイルシステムやネットワークから完全に隔離されているため、任意コード実行のリスクを抑えながらユーザースクリプトを動かせます。とはいえ、サンドボックス内のスクリプトからホスト側の関数を安全に呼び出したい場面もあります。そこで使うのがvm.newFunction()です。ホスト(外の世界)で定義したJavaScript関数をQuickJSの世界に「関数として見える値」として登録し、vm.setProp()でグローバルに公開できます。
// ホストの関数をサンドボックスに公開する
const doubleFn = vm.newFunction("double", (numHandle) => {
const n = vm.dump(numHandle); // サンドボックス側の値をホストの値に変換
return vm.newNumber(n * 2); // 戻り値もQuickJSの値として返す
});
doubleFn.consume((fn) => vm.setProp(vm.global, "double", fn));
const result = vm.evalCode("double(21)"); // サンドボックス側からdouble()を呼べる
下のサンプルでは、ホスト側で定義したdouble関数(数値を2倍にするだけ)をサンドボックスに公開し、入力欄に書いたコードから呼び出せるようにしています。初期値のdouble(21) + 1は43になりますが、doubleに文字列を渡したり、公開していない関数(例えばtriple(21))を呼んだりすると、ホスト関数を経由してもサンドボックスの外には出られないまま、QuickJS側のエラーとして返ってくる様子が確認できます。
このように、newFunction()で公開する関数を絞り込めば、ユーザースクリプトに与える権限を最小限にコントロールできます。数式評価やルールエンジンをアプリに組み込む際は、必要な関数だけをsetProp()で渡す設計にすると安全です。
まとめ
QuickJSについて、特徴から実践的な使い方までを解説しました。
- 数百KBの超軽量エンジンでES2025にほぼ完全対応
qjsでスクリプト実行、qjscでスタンドアロン実行ファイル化が可能- C APIでアプリへの組み込み、
quickjs-emscriptenでサンドボックス実行も実現
「JavaScriptをもっと身軽に使いたい」あらゆる場面で、QuickJSは頼れる選択肢になります。まずはqjs -iでREPLを触ってみて、そのコンパクトさと起動の速さを体感してみてください。公式ドキュメント(bellard.org/quickjs)には、C APIの詳細やビルドオプションも豊富に載っています。
