- scriptc は通常の TypeScript を、Node・V8・JavaScript エンジンなしで実行される小型のネイティブバイナリへコンパイルし、実際の TypeScript コンパイラの型検査と Node の動作互換性を維持する
- コード構造ごとに静的コンパイル可能かを判定し、基本的にはネイティブコード化し、
--dynamicを選んだ場合にのみ quickjs-ng で npm パッケージの JavaScript とany型コードを実行する - クラス・ジェネリクス・
async/await・例外・正規表現から Node サーバー API、fetch、npm 依存関係まで対応し、未対応構文はエラーコード・コードフレーム・修正ヒント付きで拒否する - 800本以上のプログラムを Node とネイティブバイナリで実行して出力と終了コードを比較し、AddressSanitizer と参照カウント監査でメモリエラーを検査する
- Apple Mシリーズでの計測では起動時間は約 2.4ms、静的バイナリは 170〜200KB、一般的な RSS は 1〜4MB で、動的モードと組み込み依存関係を含めるとバイナリは約 3MB になる
静的コンパイルモデル
- 別個の方言やアノテーションなしで既存の TypeScript を使い、
tsconfig.jsonの検査厳格度と TypeScript の実際のes2025ライブラリを適用する- プロジェクトに
@types/nodeがあればそれも含めて型検査する - lowering のない到達可能コードには正確な診断を出し、コンパイルを中止する
- プロジェクトに
scriptc coverageは解析した文の数、静的にコンパイルされる割合、阻害要因とエラーコードを表示する- 例では 4,481 文のうち 4,451 文、99% が静的にコンパイルされる
- 実行方式は 3 段階に明示的に分かれる
- 静的コンパイル: デフォルトモードで、JavaScript エンジンなしにネイティブコードへ変換する
- 動的実行:
--dynamicを指定すると約 620KB の quickjs-ng を含め、npm パッケージの JavaScript とany型コードを実行する- 静的コードへ渡るすべての値を実行時に検証する
- 宣言された型と値が異なれば、メモリを破壊せずに捕捉可能な
TypeErrorを投げる
- 拒否: 処理できないコードにはエラーコード、コードフレーム、たいていは修正ヒントを示し、黙って誤コンパイルしない
対応する TypeScript と標準ライブラリ
- 言語機能は単一継承クラスと動的ディスパッチ、クロージャ、ジェネリクスのモノモーフィゼーション、判別共用体、分割代入、spread、テンプレートリテラル、getter/setter とイテレータをサポートする
- 安全性が証明できる場合は動的ディスパッチを devirtualize する
- 判別共用体は TypeScript の narrowing を利用したタグ値として処理する
async/awaitはスタックフルファイバーと JavaScript に合わせたスケジューリングで実装する- 例外と
finally、省略可能・デフォルト・残余引数に対応する
- 正規表現は QuickJS が使うものと同じ ECMAScript 互換バイトコードインタプリタ を使用し、正規表現を使うバイナリにのみリンクする
- 標準ライブラリには UTF-16 セマンティクスを守る文字列、JavaScript と同じ順序・同一性ルールを持つ配列・Map・Set を含む
JSONの型変換は実行時検証を通すMath、typed array、Buffer、型付きcatchをサポートするError階層も提供する
Node および Web API
- Node API は
fs,path,process,child_process,os,crypto,url/URL,zlib, タイマーとシグナルハンドラをサポートするfsは同期および Promise API を提供するchild_processはパイプストリームをサポートする- イベントループに外部依存はない
- サーバースタックは
net,http,https,tls,dgram,dns,fs.watch,readlineを含み、実際のプロキシサーバーをコンパイルできる- TLS には同梱の mbedTLS を使用する
fetchと streams、Headers、AbortSignalなど WHATWG Web API の一部も同じネイティブのネットワーク・TLS スタック上に実装する- リダイレクト、gzip、
AbortSignal.timeout、Node 形式のエラー原因をサポートする - libcurl やシステム HTTP 依存は使わない
- リダイレクト、gzip、
npm 依存関係と動的実行
--dynamicでは Node のモジュール解決方式を使い、パッケージが提供する.d.tsを基準に型検査する- npm パッケージの JavaScript はビルド時にバイナリへ組み込まれるため、実行中に
node_modulesは読まない scriptc coverage --dynamicは各文が静的・動的領域のどちらで実行されるかと、残っている阻害要因を表示する- JavaScript エンジンは明示的に動的モードを選んだときにのみ含まれるため、バイナリサイズが黙って増えない
正確性とメモリ安全性
- 差分テスト は 800本以上のプログラムを Node とネイティブバイナリでそれぞれ実行し、stdout、stderr、終了コードをバイト単位で比較する
- 数値出力は最短ラウンドトリップ表現に従い、100万個の
doubleを Node と比較してファジング検証する - サーバーは両実装に実際のクライアントドライバを接続してテストする
- 数値出力は最短ラウンドトリップ表現に従い、100万個の
- テスト一式を AddressSanitizer と参照カウント監査の下で再実行し、リークや use-after-free があればビルドを失敗させる
- Node と意図的に異なる動作は数十件あり、主にタイミングの内部実装とエラーオブジェクトのプロパティに関係する
- 各差分は文書化され、番号が付けられ、隠れた差分は許容しない
性能特性
- Apple Mシリーズで Node、Go、Rust、Zig と同じタスク、同じバイト列の出力を基準に計測する
- 起動時間は約 2.4ms で、Node の約 47ms より短く、Zig に近く、Go・Rust を上回る
- 静的バイナリサイズは 170〜200KB で、
--dynamicと組み込み依存関係を含めると約 3MB- 比較対象として示された Go バイナリは約 2MB、Node SEA は 60〜100MB
- 一般的なメモリ使用量は RSS 1〜4MB で、Node は 67〜116MB
- ランタイムは JavaScript に合わせた
f64セマンティクスを維持しつつ、ほとんどのタスクでシステム言語と競合する- 整数推論と所有権解析はロードマップに含まれる
明示的なエスケープハッチ
comptime(() => ...)はコンパイラ内部の隔離された VM で TypeScript をビルド時に実行し、結果をリテラルとしてバイナリに埋め込む--ffiはシグネチャだけを持つ TypeScript 宣言を C ABI 呼び出しへ直接接続し、マニフェストで宣言したアーカイブ・オブジェクト・システムライブラリをリンクする- 境界は明示的で、長さ情報を含む
- 詳細な方法は Native FFI guide で確認できる
JSON.parse(...) as Configのような 検査付き型アサーション は実行時検証コードを挿入する- 検証失敗時には
expected number at $.port, got stringのように不正なパスと期待型・実際の型を含む例外を投げる
- 検証失敗時には
コンパイラ構成
- 処理過程は
TypeScript → tsc によるパース・型検査 → lowering → typed IR → C → clang → ネイティブ実行ファイルの順 packages/compilerは tsc API ベースのフロントエンド、IR 検証・シリアライズ、LLVM および C バックエンドを含む- フロントエンドとバックエンドの間のインターフェースには IR だけを使う
- LLVM がデフォルトのコード生成器で、対応範囲外のプログラムには透過的なフォールバック経路を使う
- C は恒久的な参照バックエンドとして維持され、
--backend cでソース行情報付きの読みやすい結果を生成する
packages/runtimeは参照カウントベースの値と循環回収器、スタックフルファイバー、kqueueイベントループ、サーバースタック、JavaScript 互換の数値出力を実装する- 機能単位のリンク方式を使い、実際に使う機能だけをバイナリに含める
packages/cliはscriptc build,scriptc run,scriptc coverageコマンドを提供する
インストールと開発
npm install -g scriptcでインストールし、clang が必要- 主要プラットフォームは macOS arm64 で、Linux と Windows バイナリはクロスコンパイルする
- 各プラットフォームは別個の差分テスト経路で検証する
- 開発は
pnpm install && pnpm buildで始めるpnpm testは差分テスト一式と診断スナップショットを実行するSCRIPTC_SAN=1 pnpm testは同じテストを ASan と参照カウント監査の下で実行するpnpm scriptc build x.ts --emit-irは生成された C とx.ir.jsonを保持する
- すべての機能は差分テストとともに追加され、通常テストとメモリ安全性テストの両方に通らなければマージできない
1件のコメント
Hacker Newsの意見
Vercelは月に1回くらいの頻度で話題性のあるプロジェクトを出して、信頼性と存在感を維持しようとしているように見える。真面目な会社やプロジェクトがscriptcを使うとは思えない。
貢献者には敬意を払うが、コードはいかにもClaude生成っぽく、それなのにClaudeが貢献者として表示されていないので余計に怪しい。
プロジェクト / 関連HN記事
Porfforはしばらく前から同じ目標を追ってきた。開発者のCanadaHonkは非常に優秀だが、プロジェクトは依然としてTest262の約68%しか通っていない。
プロジェクトの範囲を勘違いしているのでなければ、Vercelがこれほど速く進捗を出したやり方はかなり疑わしい。
典型的なVercel式プロジェクト。公開から5日、全部バイブコーディング、理由もなくスター1,500件を集めたが、誰の問題も解決せず、長くても数か月後にはメンテナンスが止まりそうだ
批判するだけでなくローカルの複数プロジェクトに実際に適用してみたが、どれもコード範囲の解析で数百件のエラーが出て、実質的に使えなかった。
外部ライブラリなしでゼロから書けばバイナリにコンパイルできるかもしれないが、そうならRust、Go、Zig、D、C、V、Ada、C++、Nim、Swift、Kotlin Native、Haskellのように、最初からまともなコンパイルを目標に設計された言語を使わない理由がない。
TypeScriptの利点は表現力だけでなく、巨大なnpmエコシステムとの互換性にある。ほとんどのパッケージは型宣言でインターフェースだけを定義し、実際のコードはJavaScriptとして配布するので、パッケージを使うには現実的にはJavaScriptエンジンが必要だ。
最初から新しく始めてnpmパッケージをまったく使わないつもりなら、AssemblyScriptを使うほうがよい。NodeはTypeScriptでパッケージを配布しないよう明示的に推奨しているが、それはTypeScriptがマイナーバージョン間でも後方互換ではなく、コンパイラ設定もパッケージ間で移植性がないためだ。
こういうプロジェクト1つで、HNのようなサービスのトップページに出続けられる。トークンを投じて誰も望んでいないもっともらしいプロジェクトを作り、公開で到達範囲を広げて、また繰り返すという成長戦略だ。
12か月後には、オープンソースプロジェクトの90%が実ユーザーなしで面白そうに見えるだけのバイブコーディング成果物になっているかもしれない。今では完全なコンパイラさえ簡単に生成できるが、重要なのは長期保守とコミュニティであり、人目を引くタイトルだけでユーザーは残らない。
Vercelが本気なら、実際のコストとリスクを引き受けて自社の実験的ランタイムとして採用すべきだ
とても優れた問題領域だ。AIでランタイムコードを最適化するコンパイラを作る似た取り組みをZodに適用した: zod-compiler
Zodスキーマをビルド時に単純なブール演算チェーンへコンパイルし、コード変更なしで2〜74倍高速化し、プラグインがZod呼び出しをコンパイル済みパースに置き換える。最適化の大半はClaudeが100回以上の反復で書いた。
実際のZodと結果を比較できるため、正しさを主観的に判断する必要がない点はscriptcと同じだ。基準実装とベンチマークがあるコンパイラ、シリアライズツール、フォーマッタ、クエリプランナなど全般に適用できる
ClaudeでscriptcとNodeのベンチマークを実行した。最も有利なバイト配列の結果でも、scriptcは専用最適化後でNode 24より約7.5倍遅い。
その代わり実行ファイルの起動は12倍速く(1.5ms対18.6ms)、メモリは72倍少なく(2.5MiB対181MiB)、ランタイム依存のない単一の370KB実行ファイルになる
小さくて高速なネイティブ実行ファイルが必要だと認めたのはよいが、Javaが数十年かけて通ってきた道を見ると、実用性には懐疑的だ。1990年代のGCJは技術的には悪くなかったが、エコシステムの支援がなかった。
その後GraalVM Nativeが問題をより包括的に扱うようになり、主要ライブラリやフレームワークが互換性確保に動いたが、今でも単純な既存アプリケーションですらネイティブで完全に動かすのは非常に難しい。scriptcのような試みは歓迎だが、実用化までは長く険しい道になりそうだ。
Excelsiorが消えた理由の1つは、GraalVMとOpenJ9が無料で提供されているからかもしれない。PTCとAicasは注目されにくい組み込み・リアルタイム顧客層のおかげで今でも順調に運営されている
開発が継続されるなら、.NET AOT級の大きな成果になる可能性がある。公開からまだ数日なので今は軽く試す程度だが、捨てられずに発展し続けるならエコシステムにかなり役立つかもしれない。
AIで作られたコードも人間が書いたコードと同じように品質の幅が広い。重要なソフトウェアなら、自分で書くときと同じ基準で生成し、すべてのコードをレビューすべきで、そう使うなら優れた方法だ。レビューが不足しているプロジェクトは品質や開発者の責任感が低い可能性があり、採用はさらに難しくなる。
オンラインの議論は「全部AI生成」と「絶対にAIを使わない」という両極端に流れがちだが、現実には思慮ある判断を加速する中間地点が合理的だ。そうでないソフトウェアは、低品質や放置のリスクのため使うのをためらうようになる