モノレポの闇を光に変える:TypeScript Project Referencesを極める
やあ。今日もフロントエンドの戦場でコードと格闘している君へ。
大規模なプロジェクトになると、`tsc`を叩くたびにPCが唸りを上げ、ビルド完了までの待ち時間にコーヒーを淹れに行く……そんな経験はないかな?あるいは、一つの巨大な`tsconfig.json`を複数人で編集して、コンフリクトの嵐に巻き込まれたことは?
TypeScriptの「Project References (`composite: true`)」は、そんな泥沼から僕らを救い出してくれる、いわば「大規模開発の救世主」だ。今日は、この機能をただの教科書的な説明ではなく、現場のリアルな視点から深掘りしてみよう。
—
なぜ今、Project Referencesが必要なのか?
結論から言うと、「分割」と「キャッシュ」のためだ。
通常、`tsc`はプロジェクト全体を一度に解析し、型情報をメモリに乗せようとする。プロジェクトが数万行を超えたあたりで、このアプローチは限界を迎える。コンパイル時間は指数関数的に増え、エディタの補完も重くなる。
Project Referencesを使うと、TypeScriptはプロジェクトを「独立したビルド単位」として認識する。一度ビルドされたサブプロジェクトは`.d.ts`と`.tsbuildinfo`(ビルドの証跡)としてキャッシュされ、変更がない限り再計算されない。
ブラウザがどう処理しているか? という問いに対しては、少し補足が必要だ。TypeScriptはコンパイル後にJavaScriptを出力するが、ブラウザはTypeScriptそのものを動かしているわけではない。Project Referencesはあくまで「ビルドパイプラインの最適化」であり、「ブラウザに届くJavaScriptの品質や整合性を、いかに高速かつ安全に担保するか」という、僕らエンジニアのための戦術なんだ。
—
実践:Project Referencesの構成術
現場で最も汎用的な「コアロジック」と「UI層」に分ける構成を見てみよう。
1. ディレクトリ構造
my-app/
├── packages/
│ ├── core/ # 共通ビジネスロジック
│ │ ├── tsconfig.json
│ │ └── src/index.ts
│ └── web/ # フロントエンドUI
│ ├── tsconfig.json
│ └── src/index.ts
└── tsconfig.json # ルート(全体管理)
2. 設定ファイルの書き方(ここが肝だ)
まずは共通の`core`プロジェクト。`composite: true`が必須だ。
// packages/core/tsconfig.json
{
“compilerOptions”: {
“composite”: true, // これがProject Referencesの心臓部
“declaration”: true, // 型定義ファイルを生成する(必須)
“outDir”: “dist”,
“strict”: true
}
}
次に、それを利用する`web`プロジェクト。
// packages/web/tsconfig.json
{
“compilerOptions”: {
“composite”: true,
“outDir”: “dist”
},
“references”: [
{ “path”: “../core” } // ここで依存関係を明示する
]
}
最後に、ルートの`tsconfig.json`で全体を統括する。
// tsconfig.json (ルート)
{
“files”: [], // ルート自体はソースを持たないので空にする
“references”: [
{ “path”: “./packages/core” },
{ “path”: “./packages/web” }
]
}
—
現場で直面する「落とし穴」への対策
この機能を導入すると、最初は快適だが、いくつかハマりどころがある。シニアとしてのアドバイスを置いておくよ。
1. `declaration: true`を忘れるな:
`composite: true`にすると、TypeScriptは自動的に`declaration`を有効にしようとする。サブプロジェクト間で型を共有するには、型定義ファイル(`.d.ts`)が不可欠だからだ。これをサボるとビルドが通らない。
2. `outFile`との併用は不可能:
`outFile`オプションを使っている古いプロジェクトには適用できない。現代のモジュールベース(ESM)開発なら問題ないはずだ。
3. CI/CDでのビルドコマンド:
ルートディレクトリで単に `tsc` と打つだけでは不十分な場合がある。CI上では `tsc –build –verbose` を使うのが鉄則だ。`–verbose`をつけると、どのプロジェクトが再ビルドされたのかが手に取るようにわかる。
—
最後に:エンジニアとしてのマインドセット
Project Referencesは、ただの高速化ツールではない。「疎結合な設計を強制するツール」でもあるんだ。
サブプロジェクトを分けると、不用意な循環参照ができなくなる。`core`から`web`を参照しようとすると、TypeScriptが「依存関係のループだよ」と警告してくれる。これによって、コードの依存関係が自然と整理され、テストもしやすい、堅牢なアーキテクチャへと導かれる。
面倒な設定に見えるかもしれない。だが、この一手間が、半年後の君自身やチームメンバーを、スパゲッティコードの迷宮から救い出すことになる。
さあ、エディタを開いて、まずは小さなパッケージから分割を始めてみよう。何か詰まったら、いつでも聞いてくれ。応援しているよ。

コメント