TypeScript のモノレポに対して2行のバグ修正をプッシュし、ビルドパイプラインが型チェックに10分間も費やすのを目の当たりにしたことがあるなら、あなたはその問題をすでに理解しているはずです。遅延の原因は、バンドルでも、ミニファイでもありません。TypeScript が宣言ファイルを書き出そうとする瞬間にあります。たった一つの .d.ts ファイルをエミット(出力)する前に、コンパイラは完全な型グラフを解決しなければなりません。インポートを辿り、ジェネリクスを評価し、3層下の依存関係に存在するかもしれない戻り値の型を推論します。あなたが変更したその1つのファイルが別のファイルに影響を与え、それがさらに3つ目のファイルに影響を与え、突然、コンパイラは関数の戻り値を記述するためだけに、リポジトリ全体にわたって調査作業を行うことになるのです。
TypeScript 6.0 は、その習慣を打破するために isolatedDeclarations を導入します。
真のボトルネック
宣言ファイルは、コードの公開契約(パブリック・コントラクト)です。他の開発者があなたのパッケージをインポートするとき、TypeScript はソースコードではなく .d.ts ファイルを読み取ります。これらのファイルを正しく生成するためには、コンパイラは型チェッカーをスキップすることができません。出力の最初の1行を書き出す前に、あらゆる形状、あらゆるユニオン、そしてあらゆる推論された戻り値の型を把握しておく必要があるのです。
50個のファイルしかない小規模なプロジェクトでは、これは一瞬で終わります。しかし、数千のモジュールを持つ大規模なモノレポでは、直列処理による悪夢となります。コアパッケージのユーティリティ型を変更すると、コンパイラは推論された型が依然として有効であることを検証するために、すべてのコンシューマー(利用者)を再検証します。ビルド時間は単なるファイル数ではなく、依存関係の深さに比例して増大します。些細なリファクタリングが、グラフ全体の再計算を引き起こすこともあるのです。多くのチームは、これを避けられないものとして受け入れています。しかし、そうではありません。
isolatedDeclarations の仕組み
この新しいフラグは、ユーザーとコンパイラの間の契約を変更します。TypeScript にエクスポートの型を推論させるのではなく、ユーザー自身が型アノテーションを記述します。このたった一つの転換により、グローバルな知識(全体像の把握)の必要性が排除されます。コンパイラは、モジュールの宣言をエミットするために、もはや依存関係を分析する必要がなくなります。目の前にある構文だけを見れば済むようになるのです。
これは、各ファイルが .d.ts 出力を独立して、並列に生成できることを意味します。ビルドツールは、宣言の生成を開始する前に完全な型グラフを構築する必要がありません。これまで型チェッカーを持たないために .d.ts のエミットを避けていた esbuild や swc といったツールも、今やトランスパイルに近い速度で宣言を生成できるようになります。これらのツールは、ソースから明示的なアノテーションを直接読み取り、型の方程式を解くことなく、対応する型定義を書き出すことができます。
以前は、.d.ts ファイルを生成するために必要な型情報を持っているのは公式のコンパイラだけだったため、宣言のエミットは tsc の独占状態でした。高速なトランスパイラは、型を削除したり構文を変換したりすることはできましたが、型定義を生成することはできませんでした。isolatedDeclarations によって、エコシステム全体が宣言のワークフローを扱えるようになります。変換は「論理的」なものから「機械的」なものへと変わります。この違いこそが、10分間の待ち時間を数秒へと変えるのです。
トレードオフ:明示的であることが新たなデフォルト
スピードには、単純な代償が伴います。エクスポートされるすべての関数、クラス、変数は、明示的な型アノテーションを持つ必要があります。TypeScript は、あなたの代わりに公開 API の型を推論することを拒否します。
シンプルなユーティリティを考えてみましょう:
export function getUser(id: number) {
return fetchUser(id);
}
isolatedDeclarations が有効な場合、これはエラーになります。コンパイラは fetchUser を検査することなしにその戻り値の型を知ることはできず、このフラグの下では...
