TypeScript 7の10倍速は型が重いコードでも出るのか、drizzle-orm・Effect・lobe-chatをM4 Mac miniで実測
目次
TypeScript 7が正式リリースされた。コンパイラと言語サーバーを丸ごとGoに移植したネイティブ版で、公称は「フルビルドで8〜12倍」。
リリース当日にM4 Mac mini(10コア/16GB)へ入れて、公式ベンチマークに出てこないタイプのコードベースで実測した。
実測ではファイル数が多く型がシンプルなlobe-chatで7.3倍が出た一方、型レベルの計算が重いEffectは5.3倍、drizzle-ormは3.9倍で、公称の下限に及ばなかった。
16GBのマシンだと、公式が「もっと速くなる」と紹介している--checkers 8はメモリを圧迫して逆に遅くなる。
Vue製のComfyUI_frontendではそもそも動かなかった。
TypeScript 7の変更点
2025年3月に発表されたGo移植が正式版になった。
| 項目 | 内容 |
|---|---|
| ネイティブ化 | Go実装+共有メモリのマルチスレッド。フルビルドで8〜12倍が公称値 |
| エディタ対応 | LSP(言語サーバープロトコル)を正式サポート。VS Codeは専用拡張で、数週間以内に本体同梱予定 |
| 並列制御 | --checkers(型チェックワーカー数、既定4)、--builders(プロジェクト参照の並列ビルド)、--singleThreaded |
| watchモード | ParcelのファイルウォッチャーをC++からGoに移植した新実装 |
| API | なし。7.1で新設計のAPIを出す予定。つなぎに@typescript/typescript6パッケージ(tsc6バイナリと6.0 API)が用意された |
| 互換性 | 型チェック結果は6.0互換。6.0で非推奨になったフラグはハードエラーに変わる |
ネイティブ版になってもnpmのパッケージ名はtypescriptのままで、npm install -D typescript@7で入る。
中身はesbuildと同じ方式で、@typescript/typescript-darwin-arm64のようなプラットフォーム別パッケージにネイティブバイナリが入り、bin/tscは薄いNodeラッパーになっている。
6.0の破壊的変更(strictのデフォルト化、baseUrl廃止など)はTypeScript 6.0 RCの記事に書いた。7.0ではそれらがハードエラーになる。
typescript-eslintのようにAPIを使うツールと併用する場合は、npmエイリアスで6.0系をtypescript名義に残す構成が公式から出ている。
{
"devDependencies": {
"typescript": "npm:@typescript/typescript6@^6.0.2",
"typescript-7": "npm:typescript@^7.0.2"
}
}
計測対象の選定
公式ブログのベンチマークはvscode(125.7秒→10.6秒で11.9倍)、sentry、bluesky、playwright、tldrawの5つ。いずれもファイル数が多いアプリケーションコードで、倍率は7.7〜11.9倍だった。
パースと出力(emit)はファイルごとにほぼ独立に処理でき、型チェックも既定で4つのワーカーに分かれて走る。
逆に、1ファイルの中で型のインスタンス化が深く連鎖するコードは分割しようがない。スキーマ定義から型を導出するORMや、型レベルプログラミングが中心のライブラリは、公式の表に入っていない。
そこで計測対象をこの4つにした。
| リポジトリ | 規模 | ピン留めTS | 型チェックの性質 |
|---|---|---|---|
| drizzle-orm(本体パッケージ) | 448ファイル・6.2万行 | 5.6.3 | スキーマからの型導出が支配的 |
| Effect(モノレポ全体) | 1,803ファイル・58.8万行 | 5.8.3 | 型レベルプログラミングが中心。tsc -bで35パッケージ |
| lobe-chat | 8,451ファイル・118万行 | 6.0.3 | 型がシンプルな大規模アプリ。比較用の基準 |
| ComfyUI_frontend | 2,694ファイル・52.3万行 | 5.9.3(vue-tsc) | Vue SFC込み |
lobe-chatとComfyUI_frontendは、生成AI系でおなじみのコードベースから選んだ。
lobe-chatのtype-checkスクリプトはすでにtsgo --noEmitで、ネイティブプレビュー版を本採用済みだった。今回の正式版はその安定版に当たる。
検証環境
| 項目 | 内容 |
|---|---|
| マシン | M4 Mac mini(10コア / 16GB) |
| Node | 25.3 |
| TypeScript 7 | 7.0.2(@typescript/typescript-darwin-arm64のネイティブバイナリを直接実行) |
| 比較対象 | 各リポジトリがピン留めしているTypeScript(tsc.jsをNodeで実行) |
| 計測 | /usr/bin/time -lで実時間とピークメモリ(最大RSS)を取得、各3回の中央値 |
比較側をピン留め版にしたのは、「そのリポジトリの開発者が日常見ている速度」との差を出すため。
Effectは毎回tsc -b --cleanでフルビルドに戻し、TypeScript 7側は断りがなければ既定の--checkers 4で実行した。
既存のtsconfigのままでは走らない
3つとも、既存のtsconfigのままTypeScript 7を当てると設定エラーで止まった。
drizzle-ormではbaseUrlがエラーになる。
tsconfig.json(4,3): error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration.
Use '"paths": {"*": ["./*"]}' instead.
tsconfig.json(6,12): error TS5090: Non-relative paths are not allowed. Did you forget a leading './'?
対応は2箇所で、baseUrlの行を消し、pathsを./始まりの"~/*": ["./src/*"]に書き換える(TS5090が./を要求する)。これで通った。
EffectはdownlevelIterationとesModuleInterop: falseが拒否された。実体はtsconfig.base.jsonの2行なので削除は一瞬で終わる。
フラグの削除とは別に、5.8.3から7.0に飛ぶと同梱のlib.d.tsも数世代新しくなる。EffectではUint8ArrayとArrayBufferまわりの型定義変更に引っかかり、9ファイルで34件の型エラーが出た。
packages/platform-node/src/internal/httpIncomingMessage.ts(83,5): error TS2375:
Type 'Effect<Uint8Array<ArrayBufferLike>, E, never>' is not assignable to
type 'Effect<ArrayBuffer, E, never>' with 'exactOptionalPropertyTypes: true'.
コンパイラ本体の互換性は6.0基準で作り込まれている一方、5.x系のままのdrizzle-ormとEffectでは、速度を測る前にこの設定エラーと型エラーの対処が必要だった。
結果
フルチェックの実時間(3回の中央値)。
| リポジトリ | ピン留めTS | TypeScript 7 | 倍率 |
|---|---|---|---|
| drizzle-orm | 3.70秒(5.6.3) | 0.94秒 | 3.9倍 |
| Effect(tsc -b) | 42.5秒(5.8.3) | 8.1秒 | 5.3倍 |
| lobe-chat | 112.7秒(6.0.3) | 15.4秒 | 7.3倍 |
ピークメモリ(最大RSS)。
| リポジトリ | ピン留めTS | TypeScript 7 |
|---|---|---|
| drizzle-orm | 1.1GB | 1.5GB |
| Effect | 3.1GB | 1.9GB |
| lobe-chat | 7.9GB | 9.5GB |
lobe-chatのTypeScript 6は素の状態だとNodeがクラッシュし、NODE_OPTIONS=--max-old-space-size=12288でヒープを広げてようやく完走した。TypeScript 7はネイティブプロセスなので、この種のヒープ調整自体が不要になる。
一方でピークメモリはlobe-chatだと9.5GBまで伸びた。公式が言う「メモリ削減」はビルド全体で確保する総量の話で、並列ワーカーが同時に使うピークはむしろ増えた。
Effectは--builders 8を足すと8.1秒が7.1秒(6.0倍)になった。依存グラフの中でまだ並列にビルドできるパッケージが残っていた分の差だ。
—checkersを振った結果
--checkersを1、4(既定)、8で振った。
| checkers 1 | checkers 4 | checkers 8 | |
|---|---|---|---|
| drizzle-orm | 1.24秒 / 0.8GB | 0.94秒 / 1.5GB | 1.10秒 / 1.9GB |
| lobe-chat | 22.1秒 / 7.1GB | 15.4秒 / 9.5GB | 34.6秒 / 9.1GB |
まず、シングルスレッド相当の--checkers 1でもdrizzle-ormは3.0倍、lobe-chatは5.1倍出ている。速くなった分の大半は並列化ではなくネイティブコード化によるもので、並列化の寄与は1.3〜1.4倍程度しかない。
公式ブログには、各ワーカーが共通の型情報を重複して持つ設計だと書かれている。ワーカーを増やすほど重複分のメモリと初期化コストを払うので、drizzle-ormのような小さめのプロジェクトでは8ワーカーの初期化が本体を上回り、既定の4より遅くなった。
次に、lobe-chatの--checkers 8は15.4秒→34.6秒と倍以上悪化した。ピークRSSが9GBを超えた状態で16GBの実メモリを他のプロセスと取り合い、スワップに入ったとみられる。
公式の「--checkers 8でvscodeが16.7倍」という数字は、今回の16GB機では再現しなかった。2つのリポジトリとも既定の4が最速だった。
ComfyUI_frontendでは動かない
Vue製のComfyUI_frontendにtscを素で当てると646件のエラーが出る。中身はすべて同じ種類だった。
src/components/actionbar/ComfyActionbar.test.ts(6,28): error TS2307:
Cannot find module '@/components/actionbar/ComfyActionbar.vue' or its corresponding type declarations.
.vueファイルの型はvue-tsc(Volar)がTypeScriptのAPIに組み込む形で解決している。TypeScript 7にはそのAPIがまだない。
graph LR
A[.vue SFC] --> B[vue-tsc / Volar]
B --> C[TypeScriptのAPIを<br/>組み込んで拡張]
C --> D[TS 6まで: JS実装<br/>APIあり]
C --> E[TS 7: ネイティブ<br/>APIなし・7.1で新API予定]
公式もこの点は明言していて、Vue・MDX・Astro・Svelte系のワークフロー(このブログのAstroも含む)は当面TypeScript 6.0のまま。Angularには「CLIの型チェックだけTypeScript 7、エディタはTypeScript 6」という併用構成が提示されているが、Vue系はそれも使えない。
ComfyUI_frontendのvue-tscフルチェックはコールドで20.4秒(tsbuildinfoが残っていれば4.7秒)。この時間は7.1の新APIとVolar側の対応が出るまで変わらない。
今回測った4つのうち、TypeScript 7で速くなったのはプレーンなTypeScriptで完結している3つで、ComfyUI_frontendは新APIとVolar側の対応待ちとして残った。
公式は次の機能リリースを3〜4ヶ月後の7.1としている。