プログラミング
Vercel製Scriptc: JavaScriptエンジンをバイナリに含めないTypeScript-to-Nativeコンパイラ
Scriptc by Vercel: TypeScript-to-Native compiler, no JavaScript engine in binary (github.com)
要約
Scriptcは、TypeScriptコードをJavaScriptエンジンなしで高速なネイティブ実行ファイルにコンパイルするツールです。既存のTypeScriptコードを変更することなく、Node.jsと同様の動作をするバイナリを生成できます。静的コンパイルを基本としつつ、動的実行やネイティブFFIなどの機能も提供し、パフォーマンスと正確性を重視しています。
全文翻訳
Scriptc Zero-runtime TypeScript。
Scriptcは、通常のTypeScriptを、Node.js、V8、JavaScriptエンジンをバイナリに含まず、小さく高速なネイティブ実行ファイルにコンパイルします。
$ cat fib.ts
function fib(n: number): number {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
console.log(fib(30));
$ scriptc run fib.ts
832040
$ scriptc build fib.ts && ls -la fib
-rwxr-xr-x 178K fib # 自己完結型のネイティブバイナリ、約2msの起動時間
コードへの変更は不要です。アノテーションや方言もなく、Node.jsで実行するのと同じTypeScriptを、実際のTypeScriptコンパイラで型チェックし、ネイティブにコンパイルします。
コンパイルされたものは、バイト単位でNode.jsと同一の動作をします。
インストール
$ npm install -g scriptc
clangが必要です(Xcode Command Line Toolsにプリインストールされています)。
macOS arm64がプライマリプラットフォームです。LinuxおよびWindowsバイナリはクロスコンパイルでビルドされ、それぞれ独自の差分テストレーンで検証されます。
アイデア: 目に見える静的性
ほとんどのTypeScriptは、エコシステムが想定するよりもはるかに静的です。
Scriptcは、構成要素ごとに、ネイティブコードにコンパイルできるものを判断し、それを伝えます。
$ scriptc coverage app.ts
statements analyzed 4481
compile statically 4451 (99%)
blockers:
×2 functions with optional parameters as values SC1090
×1 Promise.reject SC2020
3つのティア、常に明示的:
コンパイル済み(静的) — ネイティブコード、エンジンなし。
デフォルトであり、オプトアウトしない限り唯一のモードです。
実行中(動的)(--dynamic) — 埋め込みJavaScriptエンジン(quickjs-ng、約620KB)が、静的にできないものを実行します。npm依存関係の配布済みJS、あらゆる型のコード。
静的コードに戻る値はすべて実行時に検証されます。偽の型はメモリを破損させる代わりに、キャッチ可能なTypeErrorをスローします。
拒否 — それ以外はすべて、特定の終了コード、コードフレーム、および通常は書き換えヒントとともに失敗します。
サイレントに誤コンパイルされることは決してありません。
コンパイルされるもの
静的サーフェスは、言語と、実際のプログラムが使用する標準ライブラリをカバーします。
言語 — 単一継承と真の動的ディスパッチ(証明可能に安全な場合はデブリアライズされる)を持つクラス、JSキャプチャセマンティクスを持つクロージャ、ジェネリクス(モノモルフィゼーション)、TypeScript自身のナローイングによって駆動されるタグ付き値としての識別されたユニオン、スタックフルファイバー上のasync/await(JS正確なスケジューリング)、finally付き例外、分割代入、スプレッド、オプション/デフォルト/レストパラメータ、ゲッター/セッター、文字列/配列/Map/Setのイテレータ、テンプレートリテラル、正規表現(エンジンはQuickJSが使用するECMAScript正確なバイトコードインタプリタと同じで、正規表現を使用するバイナリにのみリンクされます)。
標準ライブラリ — UTF-16正確なセマンティクスを持つ文字列、JS正確な順序とアイデンティティを持つ配列/Map/Set、実行時検証キャスト付きJSON、Math、型付き配列とBuffer、型付きcatch付きError階層。
Node.jsのAPIサーフェス — fs(同期およびPromise)、path(バイト正確なポート)、process、パイプストリーム付きchild_process、os、crypto、url/URL、zlib、依存関係のないイベントループ上のタイマーおよびシグナルハンドラ — およびサーバースタック: net、http、https、tls(バンドルされたmbedTLS)、dgram、dns、fs.watch、readline。
実際のプロキシサーバーがコンパイルされます。
fetchおよびWHATWG Webサブセット(streams、Headers、AbortSignal)は、同じネイティブnet/TLSスタック上で動作します — リダイレクト、gzip、AbortSignal.timeout、Node形状のエラー原因。libcurlやシステムHTTP依存関係はありません。
npm依存関係(--dynamic付き) — パッケージはNode.js自身のアルゴリズムで解決され、配布されている.d.tsに対して型チェックされ、そのJSはビルド時にバイナリに埋め込まれます。バイナリは実行時にnode_modulesを読み込みません。
プログラムはTypeScriptの実際のes2025 lib(プロジェクトに@types/nodeがある場合はそれも)に対して型チェックされ、tsconfig.jsonがチェッカーの厳密性を制御します。
到達した、ローリングがないものはすべて、正確な診断であり、決して驚きではありません。
正確性
すべての変更に対して2つの強制メカニズムが実行されます。
差分テスト — すべてのコーパスプログラム(800以上のテスト)は、Node.jsとネイティブバイナリの両方で実行されます。標準出力、標準エラー、終了コードはバイト単位で一致する必要があります。
数値フォーマットはJS正確です(最短ラウンドトリップ、100万個のdoubleに対するNode.jsでのファズ検証)。
サーバーは、両方の実装に対してライブクライアントドライバーでテストされます。
メモリ安全性レーン — コーパス全体がAddressSanitizerと参照カウント監査の下で再実行されます。リークと使用後解放はビルド失敗となります。
Node.jsからの意図的な逸脱(数十個あり、主にタイミング内部とエラーオブジェクトプロパティに関するもの)は文書化され番号付けされています。サイレントに逸脱するものはありません。
パフォーマンス
Apple Mシリーズで、Node.js、Go、Rust、Zigでの同じワークロードと比較して測定されました(すべてバイト単位で同一の出力、検証済み)。
ディメンション | scriptc | コンテキスト
startup | ~2.4ms | Node: ~47ms; Zigと同等、Go/Rustより先行
binary size | 170–200KB (static) | ~3MB (--dynamic + 埋め込み依存関係)
memory (RSS) | 1–4MB (typical) | Node: 67–116MB
JS-faithful f64セマンティクス。ほとんどのワークロードでシステム言語と競合します。
整数推論と所有権分析はロードマップにあります。
エスケープハッチ
comptime(() => ...) は、ビルド時にTypeScriptを実行し(コンパイラ内の分離されたVMで)、結果をリテラルとしてバイナリに焼き付けます。
ネイティブFFI(--ffi)は、シグネチャのみのTypeScript宣言を直接C ABI呼び出しにバインドし、マニフェスト宣言されたアーカイブ、オブジェクト、およびシステムライブラリをリンクします。境界は明示的で長さで区切られています。ネイティブFFIガイドを参照してください。
--dynamic は、npm依存関係および任意のコード用のエンジンを埋め込みます。scriptc coverage --dynamic は、どのステートメントがどこで実行され、残りのブロッカーが何であるかを正確に報告します。
静的はデフォルトのままです。バイナリがサイレントにエンジンを成長させることはありません。
チェック済みキャスト — JSON.parse(...) as Config は、実行時検証を挿入し、問題のあるパス($.portで期待される数値、文字列が得られた)を名前付けしたキャッチ可能なエラーをスローします。TypeScriptのasは約束ですが、scriptcはそれを検証します。
アーキテクチャ
TS[TypeScript] -->|tsc: parse + typecheck| L[lowering]
L --> IR[typed IR]
IR --> C[C]
C -->|clang| BIN[native executable]
パッケージ/コンパイラのロード — フロントエンド(tsc API → IR)、バリデーター/シリアライザー付きIR、LLVMおよびCバックエンド。IRはエンド間の唯一のインターフェースです。LLVMはデフォルトのコードジェネレーター(そのティア外のプログラムには透過的なフォールバックあり)であり、Cは常にリファレンスバックエンドです(--backend cを介して読み取り可能で、ソース行注釈付きの出力)。
パッケージ/ランタイム — Cランタイム: サイクルトコレクタ付き参照カウント値、スタックフルファイバーとイベントループ(kqueue)、サーバースタック、JS正確な数値フォーマット。機能ユニットはリンクゲートされています。バイナリは使用したものに対してのみ支払います。
パッケージ/CLI — scriptc build | run | coverage。
開発
$ pnpm install && pnpm build
$ pnpm test # 差分コーパス + 診断スナップショット
$ SCRIPTC_SAN=1 pnpm test # ASan + RC監査下の同じコーパス
$ pnpm scriptc build x.ts --emit-ir # .scriptc/x.c および x.ir.json を保持
すべての機能は差分テストとともに提供されます。両方のレーンがグリーンであることがマージバーです。