開発

scriptcとは?TypeScriptをC経由でネイティブ化する仕組みと採用条件

scriptcは、Node.jsで動くTypeScriptやJavaScriptをそのまま受け取り、C言語のコードを経由して単体で動くネイティブ実行ファイルへコンパイルする処理系です。開発元はNext.jsを擁するVercelの実験部門Vercel Labs。npmレジストリ上の初版公開は2026年7月13日で、2026年8月10日時点の最新版は同年8月9日公開の0.0.23、ライセンスはApache-2.0と記載されています。この記事では、型付き中間表現からCを吐いてclangに渡すという処理の流れ、起動2.4ミリ秒・170KB台という公表値の中身、coverageコマンドで測る静的コンパイルの対応率、tsgoやBunとの役割差、そして受託開発の現場に持ち込んでよい条件までを整理します。

まとめ:scriptc採用可否の結論と2026年8月時点の到達点

結論から示します。2026年8月10日時点のscriptcは、依存の薄い自作CLIやバッチ処理を配布可能な単一バイナリに畳む用途では検証に値する一方、npmパッケージを厚く積んだ業務アプリ本体の実行基盤としては採用の段階に達していません。判断を分けるのは性能値ではなく、手元のコードが静的コンパイルの範囲に収まるかどうかです。

根拠は3点あります。バージョンが0.0.x系にとどまり、初版公開から1か月弱で0.0.23まで版が進んでいること。主要プラットフォームがmacOS arm64に置かれ、LinuxとWindows向けはクロスコンパイルで生成する構成であること。そしてany型やPromise.reject、JavaScriptを含むnpm依存が静的コンパイルを止め、その場合は組み込みエンジンへフォールバックする設計になっていることです。

それでも設計の狙いは明快です。V8もNodeもバイナリに含めないため、Node.jsで60〜100MB規模になっていた配布物が170〜200KBに収まり、起動も約47ミリ秒から2.4ミリ秒へ縮む。書き換えを求めずに既存のTypeScript資産をそのまま入力にできる点が、既存のネイティブ化手段と違うところです。

Vercel Labsが公開したC経由コンパイラの構造と公表値

まず出自と数字を押さえます。ここを曖昧にしたまま比較に進むと、判断を誤ります。

2026年7月にvercel-labsが公開したApache-2.0の実験プロジェクト

公開元はGitHubのvercel-labs組織で、リポジトリ名はscriptcです。ライセンスはApache-2.0。npmレジストリの記録では最初のパッケージ公開が2026年7月13日、日本語圏での報道はgihyo.jpが7月24日リリースとして伝え、Publickeyが7月28日付で取り上げています。パッケージの説明文には「no Node, no V8, no JavaScript engine in the binary」と明記されており、生成物からJavaScript実行系を外すことが設計の出発点だと分かります。

版の進みは速い。npmレジストリのメタデータでは、2026年8月10日時点の最新が同年8月9日公開の0.0.23です。7月中旬の初版から1か月弱で0.0.x系を20回以上刻んでいる計算になり、APIやフラグの挙動が固定されたと見なすには早い段階といえます。

型付きIRからC生成を挟みclangで実行ファイルを作る4段階の流れ

処理の順序はこうです。TypeScriptのソースを読み、型情報を保持したまま中間表現へ落とす。その型付きIRからC言語のソースコードを生成する。生成したCをclangに渡してコンパイルし、実行ファイルへリンクする。LLVM IRを直接吐くのではなく、いったん人が読めるCを挟むところがscriptcの構造上の特徴です。

この構成には副作用があります。--backend cを指定すれば生成されたCコードを取り出せ、--emit-irを付ければ中間表現を残せる。コンパイル結果が期待どおりでないとき、どの段階で情報が落ちたのかを人手で追える設計になっています。ネイティブ関数との接続には--ffiが用意されており、Cのライブラリを直接呼ぶ経路も確保されました。

起動2.4ミリ秒・静的170〜200KBというREADME掲載の実測条件

公表されている数値を、条件つきで並べます。READMEのベンチマーク欄では、起動時間がscriptcの約2.4ミリ秒に対しNode.jsが約47ミリ秒。バイナリサイズは静的コンパイルで170〜200KB、後述する--dynamicを付けると約3MB。常駐メモリ(RSS)は典型値で1〜4MBとされ、Node.jsの67〜116MBと並記されています。Publickeyの記事も、Node.js同梱時に60〜100MBだった配布物が170〜200KBへ縮む点を紹介しました。

ここで注意したいのは、これらが「Hello World相当ではなく実アプリの値」だと保証されていない点です。数値の桁は動かないとしても、依存を積めば静的側に残る割合が下がり、--dynamic側へ寄ればサイズは3MB前後の水準に移ります。自分のコードで測り直す前提で読む数字と考えてください。

指標 scriptc Node.js
起動時間 約2.4ミリ秒 約47ミリ秒
配布サイズ(静的) 170〜200KB 60〜100MB
配布サイズ(dynamic時) 約3MB 同上
常駐メモリRSS 1〜4MB 67〜116MB

表の出典はいずれもリポジトリのREADMEに掲載された比較値です。測定機はApple Siliconのmacとされており、x86_64のLinuxサーバで同じ比率が出るとは限りません。

静的コンパイルの適用範囲とdynamicフォールバックの境界線

scriptcの評価は、性能値ではなくこの章で決まります。どこまでがCになり、どこからJavaScriptエンジンに落ちるのか。

any型やPromise.rejectが静的コンパイルを止める具体的な条件

既定では静的コンパイルだけが走ります。型が確定していれば構造体やネイティブ関数へ落とせますが、型が消える箇所はCへの変換ができません。公開情報で名指しされている代表例を挙げます。

  • JavaScriptのコードを含むnpm依存パッケージ
  • any型が入り込んでいるコード
  • オプショナル引数を持つ関数を値として渡す書き方
  • Promise.rejectの呼び出し
  • 実行時に構造が決まる動的な処理全般

実務で最初に当たるのは、この一覧の先頭です。TypeScriptで書いた自分のコードが100%静的でも、依存パッケージの中身がJavaScriptなら、その時点で静的一本での完走はできません。判定は自分のソースではなく依存ツリー全体に及ぶ、と押さえておくと見積もりを誤りません。

quickjs-ngを埋め込むdynamicフラグとサイズが3MBに増える代償

静的化できない部分を含んだままビルドしたい場合、--dynamicを付けます。すると軽量JavaScriptエンジンのquickjs-ngがバイナリへ組み込まれ、静的化できなかった箇所だけがエンジン側で実行される構成になります。quickjs-ng自体は620KB以下と紹介されており、V8を抱えるNode.jsとは桁が違う。それでも、静的のみで170〜200KBだったバイナリは約3MBまで膨らみます。

ここは割り切りどころです。15倍前後に増えても、Node.js同梱の60〜100MBと比べれば依然として小さい。配布サイズを最重視するなら静的100%を目指してコードを削り、実行できることを優先するなら--dynamicで通す。どちらを選ぶかは、配布経路がコンテナイメージなのか、エンドユーザーへのバイナリ直配布なのかで変わってきます。

byte-for-byte互換をうたう一方で挙動差が残る配列範囲外アクセス

scriptcはNode.jsとのバイト単位の互換をうたっています。ただし、すべての挙動が一致するわけではありません。クラスメソッドの検証記事では、配列の範囲外アクセスがコンパイル自体は成功するものの、実行時の挙動がNode.jsと異なる例が報告されました。JavaScriptならundefinedが返る場面が、C経由のネイティブコードでは別の結果になり得るということです。

この一点だけで採用を諦める必要はありません。ただ、移植時のテストは「同じ入力で同じ出力が出るか」ではなく「境界値と異常系で同じ出力が出るか」を軸に組む必要があります。正常系だけの回帰テストは、この種の差分を素通りさせます。

導入手順とcoverageコマンドで対応率を測る検証工程の組み方

導入自体は数分で終わります。工数がかかるのは、この後のcoverage確認です。

npmでのグローバル導入とclangを前提とする実行環境の要件

インストールはnpmから行います。コマンドはnpm install -g scriptcの1行。前提として、Cコンパイラであるclangが必要です。macOSではXcodeコマンドラインツールに同梱されているため、開発機であればほぼ追加作業なしで揃います。Node.jsもツールチェーン側で使うため、リポジトリの.node-versionに記載された系列を用意します。

主要プラットフォームはmacOS arm64です。LinuxとWindows向けの実行ファイルはクロスコンパイルで生成する構成になっており、各ターゲットはdifferential testing、すなわちNode.jsでの実行結果との差分比較で検証されていると説明されています。CIをLinuxで回している組織では、ビルド機の構成をどう置くかが最初の検討事項になります。

run・build・coverageの3コマンドと出力される4行の読み方

コマンドは大きく3つです。scriptc runはTypeScriptやJavaScriptのファイルをその場でコンパイルして実行する。scriptc buildは実行ファイルを生成する。そしてscriptc coverageが、コードのうちどれだけが静的コンパイルできるかを事前に測ります。

  1. scriptc coverageで静的化できる割合とblockerを洗い出す
  2. blockerを潰すか、残すなら--dynamicの採否を決める
  3. scriptc runで挙動を確認する
  4. scriptc buildで配布用の実行ファイルを作る

coverageの出力は4行構成です。解析した文の総数を示す「statements analyzed」、静的コンパイルできる件数と割合の「compile statically」、--dynamic付きなら動く件数の「runs with –dynamic」、そしてどちらでも通らない「blockers」。この最後の行が空になるかどうかが、移植の可否を分ける実質的な判定になります。

console.tableがSC2020として弾かれる検証例と0.0.17系での実測

具体例があります。クラスメソッドの検証では、scriptc 0.0.17をmacOS arm64・Node.js v24.18.1・Apple clang 21.0.0の環境で実行し、3つのサンプルを測っています。単純な2文のスクリプトは100%静的、Math.cosを含むコードは50%が静的で残り50%が--dynamic側へ回る、console.tableを含むコードはblockerとして残り、エラーコードSC2020が付与されました。

読み取れるのは、標準ライブラリの中でも実装済みの範囲と未実装の範囲がはっきり分かれているという事実です。数学関数は動的側で拾えるがテーブル整形出力は拾えない、というように粒度が細かい。だからこそ、資料を読んで可否を推測するより、対象コードにcoverageを1回かけたほうが早い。移植判断の初手はここに置くのが実務的です。

tsgo・Perry・Bunとの役割差から見るscriptcの立ち位置

「TypeScriptを速くする」という言葉は、複数の別々の話題を指しています。混同したまま比較すると選定を誤ります。

型検査を速くするtsgoと実行ファイルを作るscriptcの担当領域の差

最も混同されやすいのがtsgoです。tsgoはMicrosoftがTypeScriptコンパイラ本体をGoへ移植したもので、TypeScript 7として提供される見込みの型検査・トランスパイル基盤にあたります。速くなるのは開発中のビルドと型チェックであり、成果物は従来どおりJavaScriptです。実行にはNode.jsなどのランタイムが要ります。移行の前提や版の状況はTypeScript 7とtsgoによる型検査高速化の解説記事で整理しました。

scriptcが担うのは、その先の実行形態です。ビルド時間ではなく配布物と起動時間を変える。両者は競合しません。tsgoで型検査を回しつつ、配布段階でscriptcを通すという組み合わせも理屈のうえでは成立します。

LLVM直行のPerryとC経由のscriptcで分かれる出力物と対象用途

もう1つのTypeScriptネイティブコンパイラがPerryです。PerryはRustで実装され、SWCで構文解析してLLVMで機械語を生成する構成をとり、AppKitやGTK4といったOS標準のウィジェットを呼ぶGUIアプリの構築まで射程に入れています。設計と対応プラットフォームの詳細はPerryというTypeScriptネイティブコンパイラの解説記事にまとめています。

scriptcはGUIを持ちません。CLI、バッチ、サーバサイドの処理といったヘッドレスな用途に寄っており、代わりに「既存のNode.jsコードを書き換えずに入力できるか」に軸足を置いています。デスクトップアプリを作るならPerry、既存のNode資産を小さなバイナリに畳むならscriptc、という住み分けです。

ランタイム同梱のBun compileと非同梱のscriptcで変わる配布物

単一バイナリという結果だけ見ると、Bunの実行ファイル生成機能とも似て見えます。Bunの場合、生成物にはBunランタイムそのものが同梱されるため、サイズは数十MB台になります。Bun本体の位置づけとコマンド体系はBunのインストールと使い方をまとめた記事で解説しました。

手段 成果物 ランタイム同梱 主な狙い
scriptc ネイティブ実行ファイル なし(既定) 配布サイズと起動短縮
tsgo JavaScript 別途Node等が必要 型検査とビルドの短縮
Perry ネイティブ実行ファイル なし ネイティブUIアプリ
Bunのcompile 単一実行ファイル あり 依存込みの配布簡略化

ランタイムを同梱する方式は互換性で有利です。JavaScriptがそのまま動くため、npm依存を選ばない。scriptcはその互換性を一部手放す代わりに、桁違いに小さい成果物を取りにいく設計だと理解すると、比較の軸が定まります。

受託開発の現場でscriptcを採用してよい条件と見送るべき場面

ここからは判断を言い切ります。0.0.x系という段階を踏まえたうえでの、現時点の線引きです。

依存が薄い自作CLIと定期ジョブに限れば採用が成立する3条件

採用してよいのは、次の3つを同時に満たす場合に限ります。第1に、外部npm依存がゼロか、TypeScriptで書かれた少数のパッケージだけで完結していること。第2に、coverageのblockers行が空になること。第3に、起動時間か配布サイズが解決したい課題として実在すること。

この条件に当てはまる典型は、社内向けのCLIツール、コンテナ内で秒単位に何度も起動される定期ジョブ、エンドユーザーの端末へ直接配る小さなユーティリティです。とくに起動が支配的なワークロードでは、47ミリ秒が2.4ミリ秒になる差が積算で効いてきます。1日1万回起動するジョブなら、起動オーバーヘッドだけで約7分の削減になる計算です。

本番の業務アプリでscriptcを見送るべき依存構成と失敗パターン

逆に、見送るべき条件を挙げます。ExpressやNestJSのようなフレームワーク上に構築した業務Webアプリ、ORMやSDKを多数抱えるバックエンド、そして納期と保守責任を負う受託案件の本番系。これらは対象外です。

想定される失敗はこうです。プロトタイプでcoverage 100%を確認して採用を決め、実装が進んで認証ライブラリやAWS SDKを入れた段階でblockerが噴出する。--dynamicで回避すればサイズ利得は3MB前後まで後退し、静的化されない箇所の性能はNode.jsに近づく。結果として、得られる利点が小さいまま0.0.x系のバージョン追随コストだけが残ります。着手前にフル依存ツリーでcoverageを測っていれば防げた失敗です。

0.0.23という版番号とmacOS arm64主軸を踏まえた検証の置き方

今やるべきことは、採用の可否を決めることではなく、測っておくことです。手元のツール群にscriptc coverageを一度かけ、静的化率とblockerの内訳を記録しておく。その記録があれば、版が進んで対応範囲が広がったときに、再評価の判断が数分で終わります。

ビルド環境の前提も見落とせません。主要プラットフォームがmacOS arm64である以上、LinuxのCIで完結させたい組織はクロスコンパイル構成の検証から始めることになります。こうした実行基盤の見直しを社内リソースだけで進めるのが難しい場合は、TypeScriptによる業務用・Webアプリ開発の受託先に、既存資産の棚卸しとビルド構成の設計から相談する選択肢もあります。新しい処理系を本番に載せる判断は、性能値ではなく保守の引き継ぎ先まで含めて決めるものです。

よくある質問

scriptcの導入検討でよく挙がる論点を、2026年8月10日時点の情報にもとづいて整理します。

scriptcはWindowsやLinuxでも使えますか?

実行ファイルの生成は可能です。ただし主要プラットフォームとして想定されているのはmacOS arm64で、LinuxとWindows向けのバイナリはクロスコンパイルによって生成する構成が採られています。各ターゲットはNode.jsでの実行結果との差分比較によって検証されていると説明されていますが、開発機がWindowsやLinuxのみという環境での常用実績は、公開情報からは読み取れません。まずはmacOS上での検証から始めるのが確実です。

既存のTypeScriptコードは書き換えずにコンパイルできますか?

書き換え不要が設計目標として掲げられており、Node.jsで動くコードをそのまま入力できるとされています。ただし全量が静的コンパイルされるとは限りません。any型やPromise.reject、JavaScriptを含むnpm依存が含まれていると、その部分はblockerになるか--dynamic側へ回ります。実際にどこまで通るかはscriptc coverageを実行すれば数値で分かるため、移植可否の見積もりはこのコマンドの出力を根拠にしてください。

tsgoやTypeScript 7があればscriptcは不要ですか?

役割が違うため、どちらかで代替する関係にはありません。tsgoが短縮するのは型検査とトランスパイルにかかる時間で、出力されるのはJavaScriptです。実行にはNode.jsなどのランタイムが必要になります。一方scriptcが変えるのは配布物と起動時間で、出力はランタイム非同梱のネイティブ実行ファイル。開発中のビルドが遅いならtsgo、配布サイズや起動が課題ならscriptc、という切り分けになります。

dynamicフラグを付けるとNode.jsと同じ速度に戻りますか?

同一にはなりません。--dynamicで組み込まれるのはV8ではなく軽量エンジンのquickjs-ngで、静的コンパイルできた部分はネイティブコードのまま動きます。動的側へ回った処理はエンジン経由の実行になるため、静的部分ほどの速度は出ません。バイナリサイズも170〜200KBから約3MB前後へ増えます。静的化率が低いほどNode.jsとの差は縮まる、という理解が実態に近い見方です。

本番のプロダクトに採用しても問題ありませんか?

2026年8月10日時点では推奨しません。バージョンが0.0.x系で、npmレジストリ上では7月13日の初版公開から8月9日の0.0.23まで短期間に版が進んでいます。フラグや対応範囲が変わる可能性を織り込む必要があり、納期と保守責任を負う案件には不向きです。社内ツールや検証用の小規模なジョブで実測を取り、版の安定を待ってから本番系の判断に進める順序をおすすめします。

関連記事

資料請求

RELATED POSTS 関連記事