第13章 循環参照を倒す②:barrel(index.ts)と依存方向の整え方📦➡️
この章が終わるころには、こんな状態になってるはずだよ〜!🥳🌸
- 「barrel(index.ts)」が便利だけど危ない理由を、図で説明できる🗺️
- 循環参照が出たときに、“どこから壊せばいいか”の型が分かる✂️
- 「barrelを使う/使わない」のチームルールを作れる📏✅
- ツールで循環を見つけて→直して→再発防止までできる🛡️
0) 2026年1月時点の“前提になる最新事情”だけ先にチラ見👀✨
- TypeScript の npm 最新安定版は 5.9.3(2026年1月時点)だよ📦 (npm)
- Node.js は v24 が Active LTS(2026年1月時点)で、現行の長期運用ラインとして扱いやすいよ🟩 (Node.js)
- そして最近の流れとして「型だけの import はちゃんと型だけとして書こうね」がより大事になってる(後で出てくる「import type」につながるよ)🧡 (Qiita)
1) まず結論:barrel は “外に見せる出口” にだけ置くのが安全💡🚪
barrel(だいたい index.ts)って、こういうやつだよね👇
- フォルダの中のものをまとめて再 export して
- import を短くできて
- 見た目がスッキリする✨
でも…便利なぶん、**循環参照の温床(結び目)**になりがち😵💫 dependency-cruiser のドキュメントでも、循環の“結び目”として barrel files が絡みやすいって言及されてるよ🌀 (GitHub)
2) barrel が循環を呼ぶ “いちばん多い形” 🌀📦
🍓事故パターン:同じフォルダ内で「index.ts 経由 import」してしまう
イメージはこれ👇
- components/index.ts が A と B をまとめる
- A.ts が B を使いたくて、つい components/index.ts から import
- すると…「index → A → index」の循環が完成😇
図にするとこう!
- index.ts → A.ts(export してるから)
- A.ts → index.ts(便利だから)
- index.ts → A.ts(export してるから)
- A.ts → index.ts(便利だから)
- はい循環!🌀
しかも eslint-plugin-import の import/no-cycle は「依存を辿って戻って来られる経路があるのを禁止」っていうルールだよ🕵️♀️ (GitHub)
3) よくある “barrel地雷” 4選💣📦😇
地雷①:フォルダ内コードが自分の barrel を使う(自己参照)🪞
- 「同じ層・同じフォルダ内は “直 import”」にすると一気に減るよ✅
地雷②:barrel が “再export以外” をし始める(副作用入り)💥
- 例:index.ts で polyfill を読み込む、初期化する、何か登録する… → 依存グラフが読めなくなる😵💫
地雷③:export * を盛りすぎて “依存方向が見えなくなる” 🌫️
- まとめすぎると「どこからどこへ依存してるか」が隠れる🙈
地雷④:型だけ欲しいのに “値 import” になって実行時依存が残る😱
- 型だけなら import type が効く!
- 型だけの import はコンパイル時に消えるから、実行時の循環には寄与しない(公式ドキュメントでもそう説明されてる)🧡 (Angular 日本語版)
4) 今日から使える “barrel運用ルール” テンプレ📏✅✨
ここ、超大事!チームでそのまま採用できる形にするね🫶
ルールA:barrel は “外向け API” 専用🚪
- ✅ 外の層(別フォルダ)から import する入口として使う
- ❌ 同じフォルダ内のファイルは、そのフォルダの index.ts から import しない
ルールB:barrel は “再exportだけ” 📦
- ✅ export { … } / export * from … だけ
- ❌ 初期化、登録、副作用のある処理は禁止🙅♀️
ルールC:barrel を置く場所は “少なめ” が正義🥹
おすすめはこのどれか👇
- パターン①:各フォルダに index.ts は置くけど「外部公開専用」
- パターン②:index.ts をやめて public.ts / exports.ts みたいに “意図が伝わる名前” にする
- パターン③:ルート(src/index.ts)だけにする(小〜中規模で強い💪)
ルールD:型だけの依存は import type で切る✂️🧡
- 型の相互参照があるときは、まずこれを疑う
- 最近は「型だけなら明示してね」がより重要になってるよ🧠 (Qiita)
5) ミニ演習:barrel でわざと循環を作って、ツールで見つけよう🔬🌀
5-1) 検出ツール:Madge を使うよ🕵️♀️✨
Madge は「依存グラフを可視化したり、循環参照を見つけたりするツール」だよ📈 (npm)
PowerShell でこれ👇
npm i -D madge
npx madge --circular --extensions ts ./src
5-2) サンプル構成(わざと事故る)💥
src/
ui/
index.ts
Button.ts
Dialog.ts
app.ts
src/ui/index.ts(barrel)
export * from "./Button";
export * from "./Dialog";
src/ui/Button.ts
export function Button(label: string) {
return { kind: "button", label };
}
src/ui/Dialog.ts(ここが罠😇)
import { Button } from "./index"; // ← 同じフォルダの barrel 経由 import(危険)
export function Dialog(title: string) {
const ok = Button("OK");
return { kind: "dialog", title, ok };
}
この状態で madge を回すと、循環を検出できる可能性が高いよ🌀(環境差はあるけど、狙いはここ!)
6) リファクタ演習:循環を壊す “3つの壊し方” ✂️✨
壊し方①:フォルダ内は直 import にする(最速で効く)🚀
src/ui/Dialog.ts
import { Button } from "./Button"; // ← 直 import に変更!
export function Dialog(title: string) {
const ok = Button("OK");
return { kind: "dialog", title, ok };
}
✅ これだけで「index.ts → Dialog.ts → index.ts」の輪っかが消える!
壊し方②:型だけ依存なら import type にする🧡✂️
たとえば「Dialog が Button の “型だけ” 欲しい」みたいな場面。
- 型だけの import はコンパイル時に消えるから、実行時循環を減らせるよ💡 (Angular 日本語版)
例👇
import type { ButtonProps } from "./Button.types";
型を別ファイル(Button.types.ts)に逃がすのも強いよ〜🧩
壊し方③:“契約” を切り出して依存方向を揃える📜➡️
循環って、だいたい 「A が B を知りたい」「B も A を知りたい」 っていう 相互に知り合いすぎ問題なんだよね🥲
そこで、
- **共通の言葉(契約)**だけを切り出して
- A も B も “契約だけ” を見る
にすると、依存方向がスッと整うよ🧭
イメージ👇
(悪い例)
A → B
B → A
(良い例)
A → Contract
B → Contract
7) “依存方向の整え方” を1枚で覚えるコツ🧠🧭✨

barrel を安全にするコツは、結局ここ👇
✅「フォルダの外に見せるもの」だけを barrel に集める
- 外側の層からは barrel で入る(入口)
- 内側の実装どうしは直 import(路地)
✅「入口」と「路地」を混ぜない
- 入口(barrel)を路地の中で使うと、迷路になって循環しやすい🌀
8) AI🤖に頼むと爆速になるプロンプト集🪄💌
コピペで使える形にするね〜!✨
🔍循環の原因特定
- 「この import 関係が循環してる可能性がある箇所を特定して、循環パスを文章で説明して」
✂️直し方の提案(3案ほしい)
- 「循環参照を壊すリファクタ案を3つ。①直import化 ②契約切り出し ③import type 化 を必ず含めて」
📏チームルール化(そのままREADMEに貼れる)
- 「barrel(index.ts)の運用ルールを、短い箇条書き10個で。フォルダ内でbarrelを使わないルールを必ず入れて」
9) まとめ:この章の “持ち帰りチェックリスト” ✅📦✨
- ✅ 同じフォルダ内で index.ts 経由 import してない?(最優先で疑う!)
- ✅ barrel は再exportだけになってる?(副作用ゼロ!)
- ✅ export * 盛りすぎて、依存方向が見えなくなってない?
- ✅ 型だけ依存は import type にできない?(実行時依存を減らす🧡) (Angular 日本語版)
- ✅ ツールで循環を検出できる?(madge など) (npm)
次の第14章は、みんな大好き(?)shared/utils 沼を回避する話だよ〜!🕳️🐥✨ 「barrelで見えなくなった依存」を、shared がさらにぐちゃぐちゃにしがちなので…ここで整えた方向感がめちゃ効いてくるよ😆💪