第69回ブログ:TypeScript 7.0 Beta が来た ― Go 移植で「10倍速」になった裏側と、現場が今すべきこと

TypeScript 7.0 Beta と Go 移植による高速化のイメージ

2026年4月21日、Microsoft が TypeScript 7.0 Beta を公開した。見出しに踊る「10倍速」「Go 言語に移植」という言葉だけでは、実プロジェクトに何が起きるのかは見えてこない。

この記事では、Microsoft 公式発表と、筆者が実際に tsgo を触った所感をもとに、

を順番に、なるべく具体的に書いていく。「@katsujin」


1. 何が起きたのか ― 「ブートストラップ」の終焉

TypeScript はバージョン 1 のころから、TypeScript 自身で書かれた TypeScript コンパイラを Node.js 上で動かす、というブートストラップ構造を取ってきた。これは言語の自己ホスティングとして美しく、また「言語チーム自身が言語のドッグフードを食べる」という意味でも意味があった。

ただ、その美しさには代償があった。

この構造のまま、TypeScript は数百万行規模のモノレポを抱える Bloomberg、Figma、Google、Notion、Slack、Vercel といった企業の本番現場へと侵食していった。コードベースが大きくなるほど、tsc の起動・型チェック・エミットにかかる時間は線形以上に伸び、CI の待ち時間と開発者の集中力を奪っていった。

TypeScript 7.0 は、この前提そのものを書き換える。

Built on a new native and parallelized foundation, it's already being used on multi-million line codebases.
— TypeScript チーム

Microsoft はこの 1 年強をかけて、既存の TypeScript コードベースを Go 言語に移植した。「ゼロから書き直した」のではなく、「機械的に移植した」点が重要だ。Daniel Rosenwasser はブログでこう書いている。

The new Go codebase was methodically ported from our existing implementation rather than rewritten from scratch, and its type-checking logic is structurally identical to TypeScript 6.0.

つまり、型チェックのセマンティクスは 6.0 と構造的に同一。10 年かけて積み上げたテストスイートをパスしているし、すでに Microsoft 社内外の数百万行コードベースで実戦投入されている。"Beta" のラベルに怯む必要はない、というのが彼らのメッセージだ。


2. なぜ Rust ではなく Go だったのか

公開直後から各所で繰り返された質問がこれだ。コメント欄でもまさにそのやり取りがある。

curious: why go vs rust?

リードアーキテクトの Anders Hejlsberg(C#、Turbo Pascal、Delphi の生みの親でもある)が語った選定理由は、要約するとこうなる。

  1. 全プラットフォームで最適化されたネイティブバイナリを出せる
  2. データレイアウトを細かく制御できる(構造体のメモリ配置を自分で握れる)
  3. GC によるメモリ管理が自動化されている
  4. 優れた並行処理プリミティブ(goroutine、channel)がある

ここで効いてくるのは、TypeScript コンパイラが型グラフという巨大なポインタの森を扱うソフトウェアだということだ。型ノードが相互に参照し合い、生成と破棄が頻繁に起きる。Rust の所有権モデルでこの構造を素直に書こうとすると、Rc<RefCell<_>> の海になるか、アリーナアロケータで手動管理するかのどちらかを迫られる。既存の JavaScript/TypeScript コードを「機械的に移植する」というプロジェクト目標に対して、これは大きな摩擦になる。

一方 Go なら、ポインタと GC で素直に書ける。クラス(構造体+メソッド)も書ける。並行処理は goroutine で書ける。TypeScript の AST やシンボルテーブルの構造をほぼそのままの形で持ち込める。

コメント欄の Simon Langlois がうまくまとめている。

Microsoft's decision to port the TypeScript compiler to Go instead of Rust was driven by the need for a pragmatic, high-speed "port" rather than a multi-year "rewrite".

「数年かけたリライト」ではなく「実用的な高速移植」を選んだ ― これが Go だった理由だ。


3. 「10倍速」の正体 ― 何が並列化されたのか

「ネイティブバイナリ化」だけで 10 倍にはならない。Node.js + V8 はそんなに遅くない。 10 倍の本丸は共有メモリ並列化にある。TypeScript 7.0 は、以下の 3 つのフェーズを並列に動かす。

フェーズ並列化のしやすさ7.0 での扱い
Parsing(構文解析)ファイル単位で完全独立ほぼフル並列
Type-checking(型検査)ファイル間で型情報を共有する必要あり限定的並列(後述)
Emit(JS 出力)ファイル単位で独立ほぼフル並列

問題は型チェックだ。型チェックは

ため、ナイーブに「ファイルを N 個のワーカーにばらまく」と、ワーカーごとに同じ型情報を再計算してメモリと CPU を浪費するし、最悪は結果がブレる

3-1. Checker Worker のデザイン

TypeScript 7.0 のチェッカーはこの問題に対し、固定数の type-checker worker を立てて、それぞれに「自分専用のワールドビュー」を持たせるアプローチを取った。

これにより「並列化したのに毎回違うエラーが出る」という最悪パターンを避けつつ、コア数のスケーリングを得ている。

3-2. 3 つの新フラグ

このアーキテクチャを露出させるための新しいコマンドラインフラグが 3 つ追加されている。

tsgo --checkers 4 --builders 4 --singleThreaded

--checkers <N>(デフォルト 4) 型チェッカーワーカーの数。コア数が多いマシン(ローカル開発機など)では増やすと速くなる。逆に CI ランナーのような貧弱な環境では減らした方がオーバーヘッドが少ない。ただし、稀にこの数値を変えると順序依存の結果差異が出る。チーム内で固定値を決めておくのが無難。

--builders <N>(プロジェクト参照ビルダーの並列数) モノレポで複数の tsconfig.json を持つプロジェクト参照ビルドを、複数並列に走らせる。--checkers と乗算的に効くので注意。たとえば --checkers 4 --builders 4 は最大 16 ワーカーが同時に走り、メモリを食い尽くす可能性がある。

--singleThreaded すべて単一スレッドで動かす。デバッグ、TS 6 との性能比較、外部からビルドを並列オーケストレーションしているケース、リソース極小環境で使う。

実運用での落とし所としては、

を最初のチューニングポイントにすると良さそうだ。なお、--builders は値を変えても結果は変わらない(順序非依存)と公式が明言している点も覚えておきたい。


4. インストールと、TS6 との共存戦略

ここが移行を考える上でいちばん実務的なパートだ。

4-1. インストール

npm install -D @typescript/native-preview@beta

そして tsc の代わりに tsgo を叩く。

> npx tsgo --version
Version 7.0.0-beta

挙動は tsc と互換。ただし速い、それだけ。

エディタ側は TypeScript Native Preview 拡張機能 for VS Code を入れる。LSP(Language Server Protocol)の上に作られているので、VS Code 以外のモダンエディタや、Copilot CLI のようなツールでも動く。

4-2. TS6 と並走させる

ここが地味に重要。

TypeScript 7 の Beta は tsgo という別名で動くので衝突しないが、安定版になると typescript パッケージ名・tsc 実行ファイル名を取り戻す。一方、typescript-eslint を始めとする多数の周辺ツールは、typescript パッケージから直接 import するピア依存を持っている。周辺ツールがまだ TS7 に未対応の段階で、tsc だけ先に 7 にしたいという需要が必ず出る。

そのための互換パッケージとして @typescript/typescript6 が公開されている。これは

する。つまり「tsc は TS7、ライブラリが import する typescript API は TS6」という分離が可能になる。

具体的には npm エイリアスを使ってこう書く。

{
  "devDependencies": {
    "typescript": "npm:@typescript/typescript6@^6.0.0"
  }
}

または、

npm install -D typescript@npm:@typescript/typescript6

これで import "typescript" する周辺ツールは TS6 の API を見続けながら、CLI の tsc6 で TS6 ビルドを、tsgo(将来は tsc)で TS7 ビルドを、それぞれ走らせられる。Beta 期間中はこの構成で両方の結果を CI で比較するのが現実的な検証スタイルだ。

なお Microsoft は「安定版のプログラマブル API は早くて TS 7.1」と明言している。コンパイラ API を直接叩いてカスタムツールを作っているチーム(AST 変換、独自リンター、Language Service プラグインなど)は、しばらく typescript-eslint 等と同じく外側から tsgo を叩く運用に切り替えるか、TS6 API のまま留まる判断になる。


5. 6.0 → 7.0 で「ハードエラー化」する破壊的変更

公式は「TS6 でクリーンに通るコードは TS7 でも同一に通る」と書いているが、これは 6.0 の新しいデフォルトをすべて受け入れていればという前提付きだ。5.x からいきなり 7.0 を試すと、相当数の設定が爆発する

デフォルト値の変更

オプション新デフォルト影響
stricttrue暗黙的 any、null チェック、関数型の厳格化が全部 ON
moduleesnextESM 前提。CommonJS 系のプロジェクトは設定必須
targetesnext 直前の安定版ES5 ターゲットは死亡
noUncheckedSideEffectImportstrue副作用 import のチェック厳格化
libReplacementfalse@typescript/lib-* パッケージによる lib 置換が効かなくなる
stableTypeOrderingtrue(固定)型推論の安定化、もう off にできない
rootDir./これは要注意(後述)
types[]これも要注意(後述)

rootDir の罠:src/ 配下にソースを置いて tsconfig.json をルートに置く構成だと、rootDir のデフォルト ./ のせいで出力ディレクトリ構造が変わってしまう。明示が必要。

{
  "compilerOptions": {
    "rootDir": "./src"
  },
  "include": ["./src"]
}

types の罠:これまで暗黙的に node_modules/@types/* が全部読み込まれていたのが、明示リストにしか反応しなくなる@types/node@types/jest に依存していて気づかない人が多そうな箇所。

{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}

旧挙動に戻すなら "types": ["*"]

削除されたフラグ・構文

ここからが本番。「もう動きません」になったものたち。

このリストを眺めると、「TypeScript で書かれた古いビルド設定」がそのまま 7.0 で動く可能性はかなり低いことがわかる。とくに baseUrl を使ったパスエイリアスは多くのプロジェクトで使われているし、module: commonjs でやっている Node.js プロジェクトはモジュール解決の見直しが必須になる。

移行戦略の現実解

公式が推奨しているのは「まず TS6 に上げて、6 の上で新しいデフォルトと向き合ってから 7 に行く」という二段ロケット。一足飛びに 5.x → 7.0 を試みると、コンパイラの 10 倍速さに辿り着く前に設定エラーで何時間も溶かす。


6. JavaScript(JSDoc)プロジェクトへの地味な大変更

意外と見落とされそうなのが、.js ファイルに対する型解析の再設計だ。

これまで TypeScript は、JS ファイルに対しては「JSDoc コメント+人気のコーディングパターン」を組み合わせて型を推論していた。Closure Compiler 風の @class@enumfunction(string): void 形式の関数型、this の別名付け、prototype 全置換などの「ふんわりした JS 慣習」を特別扱いしていた。

TypeScript 7.0 では、この特別扱いをやめる方向に振り切った。.ts での解析と一貫させる。具体的には:

Before(TS6 まで)After(TS7)
@param {someVar} x のように値を型として使えるtypeof someVar を明示する
@enum を特別解釈(typeof YourEnum)[keyof typeof YourEnum] パターンを使う
単独 ? を型として使えるany を使う
@class で関数をコンストラクタ扱いclass 宣言を使う
後置 ! で non-null 表明T のまま書く
/** @typedef {T} */ Name; の隣接記法/** @typedef {T} Name */ 形式に統一
function(string): void の Closure 風記法(s: string) => void に統一
this 別名・prototype 全置換の特別扱い廃止

JSDoc で頑張ってきたレガシー JS 資産は、動くけれど型がつかなくなるという症状が出始める可能性が高い。差分の詳細は microsoft/typescript-go の CHANGES.md で追跡されている。


7. エディタ体験 ― LSP の上で動く意味

TypeScript Native Preview 拡張機能は、コマンドラインと同じネイティブバイナリを Language Server として動かす。これが効くのは、

といった、文章では伝わりにくいが体感では明らかな部分だ。Daniel Rosenwasser も「a much more lightweight and fluid editing experience」と書いている。

機能面では、Beta 時点で

が揃っており、まだ来ていないのは「セマンティクス強化シンタックスハイライト」「より細かい import 管理コマンド」といった枝葉だけ。日常使いの満足度は十分高い、と評されている。

LSP ベースなので、Neovim、Helix、Zed、Cursor、そして Copilot CLI などからも同じ体験が得られる。「IDE のためにバイナリを別個に用意する」発想を捨てて、「LSP に統一して全エディタ共通で速くする」という設計判断は、地味だが今後数年効いてくる。


8. 現場が今すぐやれる 3 つのこと

ここまで読んで、「で、結局月曜から何をすればいいの」となったあなたに、優先順位順に。

① まず開発機の VS Code に Native Preview 拡張を入れる 今のプロジェクトに何の影響も与えずに、エディタ体験だけを試せる。リスクほぼゼロ。これが一番ハードルが低い検証で、しかも体感差がいちばん大きい。

② CI に並走ジョブを追加する 既存の tsc ジョブはそのままで、tsgo --noEmit だけ走らせる並走ジョブを追加する。@typescript/native-preview@beta を devDependency に足すだけで済む。差分が出たら issue tracker に投げる。これで自プロジェクトの 6→7 互換性が見える

# .github/workflows/typecheck.yml (抜粋)
- run: npx tsc --noEmit            # 既存
- run: npx tsgo --noEmit           # 追加(失敗しても warning 扱い)
  continue-on-error: true

③ tsconfig.json を 6.0 互換へ寄せておく これは時間がかかる作業なので早めに。とくに

このあたりは、7.0 が GA する前に終わらせておきたい宿題だ。


9. これからのロードマップ

公式が示しているスケジュールは以下。

つまり安定版まで、おそらくあと 2 ヶ月以内typescript-eslint などの周辺エコシステムも、安定版を待ってから本気の対応に乗り出すはずだ。


10. まとめ ― ブートストラップを捨てた合理性

TypeScript が自分自身で書かれていることは、コミュニティにとってある種のアイデンティティだった。「私たちの言語は、私たちの言語で動く」。それを Microsoft は捨てた。

捨てた理由はシンプルで、TypeScript はもう「JavaScript の少し型がついた版」ではないからだ。Bloomberg、Figma、Google、Notion、Slack、Vercel ― この規模のコードベースで動く言語処理系には、JavaScript ランタイムの天井よりも、ネイティブコード速度と並列性のほうがはるかに価値がある。

そして注目すべきは、書き直さずに移植したという選択。型チェッカーのロジックを構造的に同一に保ち、10 年分のテストスイートを担保にして、「速くなった以外、何も変わらない」状態を目指した。これは派手な発表の裏で、極めて保守的で実務的な意思決定だ。

10 倍速は確かに見出しに使える数字だが、私が個人的にいちばん大きいと思っているのは、LSP に統一されたエディタ体験のほうだ。Beta を 1 日触ってみて、補完が出るまでの「あの 200ms」が消える感覚は、CI ビルド時間の短縮よりも日々の集中力に効く。

npm install -D @typescript/native-preview@beta で、明日から試せる。 試して、Issue を投げよう。あと 2 ヶ月で、tsc はもう Go バイナリだ。


参考リンク

← ブログTOPへ戻る