TypeScriptの「地獄のビルド時間」を終わらせる:Project References (composite) 徹底攻略ガイド
どうも。フロントエンドの現場で日々、コンパイルの待ち時間にコーヒーを淹れに行っている君たちへ。
プロジェクトが巨大化してくると、「たった一行の修正」に対して、なぜか数分間もビルドを待たされるあの悪夢。あれ、本当に時間の無駄だよね。今回は、そんな大規模開発のボトルネックを解消するための最終兵器、TypeScriptの「Project References (`composite: true`)」について、現場の知見を交えて徹底的に解説する。
—
なぜ「Composite」が必要なのか?
TypeScriptのコンパイラである `tsc` は、デフォルトでは「プロジェクト全体を一つの巨大な塊」として扱う。つまり、どこか一箇所を触ると、依存関係を再帰的にチェックして全体を再ビルドしようとするんだ。これは小規模なら問題ないが、数百ファイルを超えたあたりで如実にパフォーマンスが落ちる。
`composite: true` を使うということは、「このプロジェクトを独立したモジュールとして扱い、ビルド結果をキャッシュ(.tsbuildinfo)する」という宣言だ。これにより、変更があったサブプロジェクトだけを再コンパイルすれば良くなる。これが、モノレポ環境や巨大アプリケーションにおける「ビルド爆速化」の核心だよ。
—
実践:Project References の構成案
まずは、実務でよくある「Core(共通ロジック)」と「App(フロントエンドアプリ)」に分ける構成を見てみよう。
1. `packages/core/tsconfig.json`
まずは共通ライブラリ側。重要なのは `composite: true` と `declaration: true` だ。型定義ファイル(.d.ts)を出力しないと、参照元が型を解決できないからね。
{
“compilerOptions”: {
“composite”: true, // これが今回の主役。プロジェクトを独立させる
“declaration”: true, // 参照プロジェクトのために型定義ファイルを生成する
“declarationMap”: true, // ソースと型定義を紐付ける(デバッグ時に便利)
“outDir”: “dist”, // ビルド成果物の出力先
“strict”: true,
“module”: “esnext”
},
“include”: [“src”]
}
2. `packages/app/tsconfig.json`
次に、それを利用するアプリケーション側。ここで `references` を使って依存関係を明示する。
{
“compilerOptions”: {
“composite”: true, // アプリ側もcompositeにすると、さらに上位のプロジェクトから参照できる
“outDir”: “dist”,
“strict”: true
},
“references”: [
// ここで依存先を直接指定する。これだけでtscはビルド順序を自動管理する
{ “path”: “../core” }
]
}
—
ブラウザはどう処理しているのか?
ここで一つ勘違いしてはいけないのは、「TypeScriptのプロジェクト参照は、ブラウザの実行時処理とは無関係」ということだ。
ブラウザが実行しているのは、TypeScriptがトランスパイルした後の「JavaScript」だ。`tsc` のビルドプロセスは、あくまで「型安全性の担保」と「モジュール解決の最適化」を行うための作業台。
具体的には、`tsc` が `composite` を通じてビルドを行う際、内部的には以下のことが起きている:
1. 増分ビルドの最適化: `.tsbuildinfo` を読み込み、前回ビルド以降に変更がないファイルはコンパイルをスキップする。
2. 型定義のリンク: `core` の `dist` 内にある `.d.ts` を読み込むことで、アプリ側は `core` のソースコード全体を解析せずに型チェックを完了できる。
つまり、「ビルド時間を削り、開発者の精神衛生を保つためのツール」と割り切るのが正しい理解だ。
—
現場で役立つ「鉄則」Tips
最後に、僕が現場でよく後輩にアドバイスしているポイントを3つ置いておく。
- `–build` オプションを忘れるな:
単に `tsc` を叩くのではなく、`tsc –build` (または `tsc -b`) を使おう。これを使わないと、せっかくの `composite` の恩恵が受けられない。npm scriptsには必ず `-b` を仕込んでおくこと。
- 循環参照は死を招く:
`A -> B` と `B -> A` のような相互参照を作ると、`composite` は即座にエラーを吐く。これはTypeScriptが悪いのではなく、設計が破綻しているサインだ。共通部分をさらに細かい `shared` パッケージに切り出す勇気を持とう。
- モノレポツールとの併用:
Turborepo や Nx を使っている場合、それらのツールがこの `composite` の仕組みを裏で活用していることが多い。素の `tsc` で構造を理解しておくと、こうしたツールが裏で何をしているのかが手に取るようにわかるようになるはずだ。
—
最後に
「ビルドが遅い」と嘆くのは、まだ君がTypeScriptのポテンシャルを使い切れていない証拠だ。`composite` を導入するのは最初は少し手間に感じるかもしれない。だが、一度この「モジュール境界」を意識した設計が身につけば、どんな巨大なコードベースを渡されても動じなくなる。
さあ、エディタに戻って、君のプロジェクトを整理してみよう。それが、シニアエンジニアへの第一歩だ。応援しているよ。

コメント