TypeScriptの「境界」を制する者:`files`, `include`, `exclude` で構築する堅牢なアーキテクチャ
大規模なフロントエンド・プロジェクトにおいて、TypeScriptコンパイラ(`tsc`)は単なるトランスパイラではない。それは、プロジェクトの「静的な知能」を管理する心臓部だ。しかし、多くの開発現場では `tsconfig.json` の設定が「とりあえず動けばいい」というレベルで放置され、結果としてIDEの補完が重くなったり、ビルド時間が肥大化したりする悲劇をよく目にする。
今回は、`files`, `include`, `exclude` を単なるパス指定のテクニックとしてではなく、「コンパイル対象を制御することで、エディタのメモリ消費とビルドパフォーマンスを最適化する戦略」として深掘りしていく。
—
1. なぜ「対象の制御」がパフォーマンスに直結するのか
TypeScriptの言語サービスは、プロジェクトに含まれるすべてのファイルをAST(抽象構文木)としてメモリ上に展開しようとする。もし、プロジェクトのルートに `node_modules` や巨大なテストデータ、あるいはビルド生成物が混入していれば、言語サービスはそれらも解析対象として抱え込むことになる。
特に、`include` を広範囲に設定しすぎると、VS Codeの「型チェックの遅延」や「補完のフリーズ」を誘発する。我々が目指すべきは、「必要なファイルだけをピンポイントで射抜く」という外科手術のようなアプローチだ。
2. files, include, exclude の賢い使い分け
これら3つのプロパティは、適用される優先順位と役割が明確に異なる。
- `files`: 明示的なエントリーポイント。ごく小規模なライブラリや、特定の依存関係だけをコンパイルしたい場合に使う。
- `include`: 広く対象を取り込むための「広域設定」。通常はここをベースにする。
- `exclude`: `include` で取り込んだものの中から「不要なもの」を切り捨てるための「フィルタ」。
推奨される構成例:モジュール境界を意識する
{
“compilerOptions”: {
“module”: “esnext”,
“target”: “es2022”
},
// include は「ソースコードが存在するディレクトリ」に限定する
“include”: [
“src//”,
“types//”
],
// exclude で不要なノイズを完全に遮断する
“exclude”: [
“node_modules”, // 基本中の基本だが忘れがち
“/.spec.ts”, // テストファイルは別の tsconfig で管理するのが吉
“/dist”, // ビルド成果物が混入すると循環参照や二重定義の温床になる
“/__tests__” // テストコードの型チェックは本番ビルドには不要
]
}
3. 上級者のための「tsconfigの多層化」戦略
もし君のプロジェクトが、UIコンポーネント、サーバーサイドのAPI定義、そしてユニットテストを同一リポジトリで管理しているなら、`tsconfig.json` を一つで賄おうとするのは止めるべきだ。
特に、テストコードの型チェックに本番と同じ厳格なルールを適用すると、ビルドパイプラインが壊れることがよくある。以下のように、`tsconfig.base.json` から継承させる構成が現代の標準だ。
tsconfig.app.json (アプリケーション用)
{
“extends”: “./tsconfig.base.json”,
“include”: [“src”],
“exclude”: [“src//.test.ts”] // アプリコードにはテストを含めない
}
tsconfig.test.json (テスト用)
{
“extends”: “./tsconfig.base.json”,
“include”: [“src//.test.ts”, “test//.ts”],
“compilerOptions”: {
“noEmit”: true // テストは実行時に型チェックが通ればいいのでJS出力は不要
}
}
4. 陥りやすい「見えない罠」を回避する
罠1:`node_modules` の再帰的読み込み
`exclude` を書かなくても、TypeScriptはデフォルトで `node_modules` を除外する。しかし、プロジェクトの設定が複雑化してくると、誤ってライブラリのソースコードを `include` に含めてしまうことがある。これはIDEのメモリ使用量を爆発させ、最悪の場合、開発用マシンをスワップ地獄に叩き込む。
罠2:`include` の過剰指定による非同期競合
`include` に巨大なライブラリの型定義を直接指定すると、コンパイラが「どの型が正しいか」の解決(Type Resolution)に時間を要し、ホットリロードが極端に遅くなる。型定義の解決は `compilerOptions` の `types` フィールドで制御し、`include` にはあくまで「君が書いたコード」だけを渡すのが鉄則だ。
結論:境界は「守り」ではなく「攻め」
TypeScriptの環境構築は、単なるメタデータの設定ではない。コンパイラという強力なエンジンに、どこを全力で走り、どこを無視すべきかを教えるチューニング作業だ。
`include` と `exclude` を整理することは、単にビルド時間を短縮するだけでなく、型定義の衝突という「重大なバグの芽」をプロジェクトの初期段階で物理的に摘み取る行為でもある。
君が書くコードがどれほど洗練されていても、コンパイルの土台が揺らいでいては、その真価は発揮されない。まずは今の `tsconfig.json` を開き、本当にコンパイルすべきファイルだけが対象になっているか、改めて確認してほしい。それが、世界最高峰のフロントエンド・アーキテクトへの第一歩だ。

コメント