【実務・中級編】 compilerOptions.typeRoots – TypeScript実践ガイド

`typeRoots`の深淵:なぜあなたのTypeScriptは「型」を見失うのか?

現場でコードを書いていると、ふと壁にぶつかる瞬間があるはずだ。「なぜか特定の型定義が認識されない」「カスタムの型定義ファイルを置いているのに、コンパイラが無視する」。

そんな時、多くのエンジニアが `tsconfig.json` の `typeRoots` を適当にいじって、結局沼にハマる。今日は、TypeScriptのコンパイラが裏側でどうやって型を探しに行っているのか、その「型解決の旅路」を紐解きながら、`typeRoots` の正しい付き合い方を伝授しよう。

—

1. そもそも `typeRoots` とは何者か?

TypeScriptが型定義(`.d.ts`ファイル)を探す際、デフォルトでは `node_modules/@types` を見に行く。これは「全世界のライブラリがそこへ型を置く」という暗黙の了解があるからだ。

しかし、大規模なモノレポ開発や、社内共有のユーティリティライブラリ、あるいは特定のビルド環境で生成された型定義を使いたい場合、デフォルトのパスだけでは太刀打ちできない。そこで登場するのが `typeRoots` だ。

「`typeRoots` を指定するということは、TypeScriptコンパイラに『お前が探すべき宝箱はここだ』と地図を渡すこと」に他ならない。

注意:初心者が陥る「全否定」の罠

ここで一つ、金言を授けよう。`typeRoots` を書くとき、`node_modules/@types` をリストから外してはいけない。

もしあなたが `typeRoots: [“./my-custom-types”]` とだけ記述すると、TypeScriptは「おっ、カスタムディレクトリだけ見ればいいんだな?」と判断し、`node_modules/@types` を一切検索しなくなる。結果、`@types/react` も `node_types/jest` もすべて行方不明になり、エディタが真っ赤に染まる。これは現場で最も多い「悲劇」だ。

—

2. 実践:正しく `typeRoots` を設定する

実務でカスタムの型定義ファイルを管理するディレクトリ(例えば `types/`)がある場合、以下のように記述するのが正解だ。

{
“compilerOptions”: {
// デフォルトの node_modules/@types も忘れず含めるのが鉄則
“typeRoots”: [
“./node_modules/@types”,
“./src/types”
],
“moduleResolution”: “node”
}
}

なぜこれがベストプラクティスなのか?

TypeScriptコンパイラは、`typeRoots` に指定されたパスを上から順番に探索する。つまり、もし同じ名前の型定義が `node_modules` と `./src/types` の両方に存在した場合、後で記述したものが優先される(あるいは衝突の警告が出る)。

この優先順位を制御できるのが最大のメリットだ。ライブラリ側の型定義にバグがある際、一時的に `./src/types` に同名の定義を配置してパッチを当てる、といった「緊急回避」が可能になる。

—

3. ブラウザには「型」は存在しないという現実

ここで視点を変えよう。なぜTypeScriptはこれほどまでに複雑な型探索をするのか?

ブラウザ(V8エンジンなど)は、TypeScriptの型定義ファイルを一切読み込まない。JavaScriptにコンパイルされた後のコードしか彼らは知らない。`typeRoots` はあくまでコンパイル時の開発者体験(DX)を最大化するための「地図」であり、本番環境の実行速度には1ミリも影響しない。

だからこそ、型定義の管理をこじらせてビルドエラーを頻発させるのは、開発効率を著しく下げる「負債」になる。型定義の置き場所を整理することは、チームの生産性を守るためのアーキテクチャ上の責務だ。

—

4. プロの現場で使う「型定義の構成例」

最後に、中級エンジニアの君に推奨するディレクトリ構成を紹介する。

root/
├── node_modules/
├── src/
│ ├── types/
│ │ ├── global.d.ts // プロジェクト全体で使うグローバルな型定義
│ │ └── api-response.d.ts // APIレスポンスの型など
│ └── index.ts
├── tsconfig.json

`src/types/global.d.ts` の中身例:

// 外部ライブラリの型定義を拡張したり、グローバルな型を定義する
declare global {
interface Window {
// 外部SDKなどがwindowに付与するプロパティを型安全に
__ANALYTICS_ID__: string;
}
}

// 必須のインポート
export {};

このように、`global.d.ts` を一つ作ってそこに型をまとめておけば、`tsconfig.json` の設定がシンプルに保たれる。無理に `typeRoots` を複雑にするよりも、型定義を整理する方がよほど健全だ。

—

まとめ:設定を「魔法」にしないために

1. `typeRoots` を弄るなら、必ず `node_modules/@types` を含める。
2. 基本は `include` や `files` で解決できないか検討する。 (実は `typeRoots` を触らずとも、`include` で個別の型ファイルを読み込ませる方が管理が楽なケースも多い)
3. 型定義は「コードの一部」である。 どこに何があるか、チーム全員が把握できる構成を目指そう。

TypeScriptは強力なツールだが、それは正しく設定したときだけ牙を剥かない。今日から君の設定ファイルが、誰が見ても迷わない「地図」になることを期待している。

何か詰まったら、いつでも聞くといい。現場からは以上だ。

コメント

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