【テクニカル・上級編】 Project Referencesによる大規模プロジェクトの分割 – TypeScript実践ガイド

モノレポの迷宮を抜け出す:TypeScript Project Referencesという「解」

大規模なフロントエンド・アーキテクチャに長く身を置いていると、ある日突然「型チェックが終わらない」という現実に突き当たる。ファイル数が数千を超え、依存関係がスパゲッティのように絡み合ったtsconfig.jsonを前に、開発者は祈るような気持ちで `tsc` を叩く。だが、そのビルド時間はもはやコーヒーを淹れる時間では足りない。

今日は、そんな泥沼を回避するための究極の武器、TypeScript Project Referencesについて語ろう。これは単なるビルド高速化のテクニックではなく、複雑なアプリケーションの依存関係を「物理的に遮断」し、メモリの消費効率を劇的に改善するためのアーキテクチャ戦略だ。

—

なぜ「巨大なtsconfig」が癌なのか

現代のTypeScriptコンパイラ(`tsc`)は、一つの巨大なプロジェクトを扱う際、全てのファイルを一つのメモリ空間にロードし、AST(抽象構文木)を構築する。これが限界を超えると、コンパイラはガベージコレクションの嵐に翻弄され、型定義の解決(Type Resolution)に膨大なCPUサイクルを費やすことになる。

Project Referencesの真髄は、「プロジェクトを型定義という名の『契約』で結ばれた独立したサブセットに分割する」ことにある。これにより、コンパイラは変更のあったプロジェクトのみを再ビルドし、他のプロジェクトは先行して生成された `d.ts` ファイルを読み込むだけで済むようになる。これが、メモリ効率とビルド速度の革命だ。

—

実装の勘所:compositeフラグとreferencesプロパティ

まずは、プロジェクトを分割するための最小構成を見てほしい。`composite: true` を設定することが、コンパイラに対して「このプロジェクトは他のプロジェクトの一部として機能するぞ」と宣言する合図となる。

親プロジェクトの `tsconfig.json`

{
“compilerOptions”: {
“declaration”: true, // compositeには必須。型定義を生成する必要がある
“declarationMap”: true, // デバッグ用にd.tsとソースをマッピングする
“composite”: true, // これが魔法のスイッチ。増分ビルドを可能にする
“module”: “esnext”,
“target”: “esnext”
},
“references”: [
// 依存先を明示的に指定。これで依存グラフが確定する
{ “path”: “../core-lib” },
{ “path”: “../shared-ui” }
]
}

ここで重要なのは、「依存関係を一方通行にする」という規律だ。循環参照が発生した場合、コンパイラは即座にエラーを吐く。これは一見不便に思えるが、実は「設計が腐っているぞ」というコンパイラからの強力な警告だ。この制約を逆手に取り、疎結合なアーキテクチャを強制するのが上級者の流儀である。

—

パフォーマンスを最適化する「3つの鉄則」

単に設定を分けるだけでは不十分だ。大規模環境で真のパフォーマンスを引き出すためには、以下の観点を意識せよ。

1. `incremental` との併用

`composite: true` は内部的に `incremental: true` を含んでいる。これにより、以前のビルド結果が `.tsbuildinfo` ファイルとしてキャッシュされる。CI/CDパイプラインにおいてこのファイルをキャッシュすることで、変更のないパッケージのビルドを完全にスキップできる。

2. 型定義の汚染を避ける

各プロジェクトが外部に公開する型は、可能な限り最小限に絞るべきだ。`internal` な型が `public` APIに漏れ出ると、TypeScriptは不要な計算を行い、依存先のプロジェクトにも不要な型チェックを強いることになる。

// 悪い例:内部の型がそのままexportされている
export interface InternalUserConfig { / …膨大な型定義… / }

// 良い例:必要なものだけを抽出して公開する
type PublicConfig = Pick;
export { PublicConfig };

3. 非同期処理と競合の回避

Project Referencesを利用すると、ビルドが並列化される。複数のプロジェクトが同じディレクトリにビルド成果物を吐き出すと、書き込み競合が発生する。必ず `outDir` を各プロジェクト内で完結するように設定し、ファイルシステム上の衝突を物理的に排除すること。

—

伝説のアーキテクトからの助言

最後に、現場でよくある「ハマりどころ」を共有しておこう。

Project Referencesを導入すると、IDE(VS Codeなど)の挙動が劇的に改善する。なぜなら、各プロジェクトが独立した `Language Service` を持つようになるからだ。しかし、たまに「型定義が見つからない」という幽霊のようなエラーに遭遇する。これは大抵の場合、`tsconfig.json` の `include` パスがプロジェクトの境界を越えてしまっていることが原因だ。

「一つのプロジェクトの `include` は、そのプロジェクトのディレクトリ内のみを監視せよ」。

この単純なルールを守るだけで、TypeScriptの挙動は驚くほど安定し、あなたの開発環境はストレスフリーな領域へと到達する。

TypeScriptという巨大な宇宙を制御するのは、魔法ではない。規律だ。Project Referencesを使いこなし、ビルドの待ち時間という「負債」を完済しよう。それが、真に堅牢なプロダクトを生み出すための第一歩だ。

コメント

タイトルとURLをコピーしました