第16章 ルールを自動で守る:ESLint境界ルール+依存の見える化🛡️📈🏁
ここまでで「依存の向き(外→内)を守るのが大事!」って分かってきたと思うんだけど… 人の注意力だけで守り続けるのはムリゲーになりがち🥲💦
だから第16章はこうするよ👇
- ✅ ESLintで“境界ルール”を強制して、うっかり違反を防ぐ🛡️
- ✅ **依存グラフを“見える化”**して、構造の健康診断をする📈
- ✅ CIに入れて未来の自分を救う🤖🏥
16-0 今日のゴール🎯✨

「お願い」じゃなくて「仕組み」で守る状態にするよ💪😊 例:
src/domainがsrc/adaptersをimportしたら 即エラー🚫- 循環参照(A→B→A)ができたら 即エラー🌀
- PRで毎回同じ確認をしなくて済むようにする🎉
16-1 まず“今どきESLint”の前提だけ押さえる🧠⚡
最近のESLintは、flat config(eslint.config.*)が主流だよ〜📦✨
そして ESLint v10は「古い.eslintrc方式を完全に消す」方向で進んでて、2026年に向けてRC段階に入ってるよ(2026-01 時点)🧭(ESLint)
なのでこの章は、flat config前提で作るね😊
16-2 使う道具セット🧰✨
A) ESLintで境界を守る🛡️
- typescript-eslint:TSをESLintでちゃんと解析するための公式スタック📘(TypeScript ESLint)
- eslint-plugin-boundaries:フォルダ(=層)ごとの import 制限ができるやつ🧱🚫(JS Boundaries)
- eslint-plugin-import:循環参照チェック(
import/no-cycle)など👀🌀(GitHub)
B) “依存の見える化”📈
- dependency-cruiser:依存を解析して、ルール検証やグラフ出力ができる🚢🗺️(循環検出もできるよ)(npm)
- madge:循環参照の検出&依存グラフ生成がサクッとできる🕸️(GitHub)
- Graphviz:
dotファイルをSVGとかに変換して“絵”にする🖼️(wingetで入れられる)(Graphviz)
16-3 セットアップ(npmでまとめて入れる)📦✨
ターミナルでこれ👇(devDependenciesでOK)
npm i -D eslint typescript-eslint eslint-plugin-boundaries eslint-plugin-import dependency-cruiser madge
ちょい安全メモ🧯 2025年に Windows環境でpostinstall悪用のnpm侵害が話題になったことがあるから、ロックファイル(package-lock等)と
npm auditは定期的にね🙏🪪 (特にPrettier系を追加する時は、変なバージョンを踏まないよう注意⚠️)(SafeDep)
16-4 ESLint:境界(層)ルールを“自動化”する🧱🚫✨
ここではこの層を前提にするよ👇(ロードマップのやつ🧅)
src/domain/**…中心(ルール)🧡src/app/**…手順(ユースケース)🩵src/adapters/**…外部I/O(DB/HTTP/SDK)💛src/contracts/**…層をまたいで参照していい“契約”📜src/shared/**…小道具(純粋関数とか)🧰
16-4-1 eslint.config.mjs を作る📝
プロジェクトルートに eslint.config.mjs を作って、こんな感じ👇
(※サンプルなので、最初はコピペで動かしてOK!)
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import boundaries from "eslint-plugin-boundaries";
import importPlugin from "eslint-plugin-import";
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
plugins: {
boundaries,
import: importPlugin,
},
settings: {
// boundaries が「どのファイルがどの層か」を判定するための設定
"boundaries/include": ["src/**/*"],
// どの層がどのパターンか
"boundaries/elements": [
{ type: "domain", pattern: "src/domain/**/*" },
{ type: "app", pattern: "src/app/**/*" },
{ type: "adapters", pattern: "src/adapters/**/*" },
{ type: "contracts", pattern: "src/contracts/**/*" },
{ type: "shared", pattern: "src/shared/**/*" },
],
},
rules: {
// ✅ どのファイルも「どれかの層」に属してね(迷子防止)
"boundaries/no-unknown-files": "error",
// ✅ 層(element type)ごとの import ルール
// デフォルトは「禁止」にして、許可ルールだけ書くのが安全😌
"boundaries/element-types": [
"error",
{
default: "disallow",
rules: [
// domain は「domain / contracts / shared」だけOK
{ from: "domain", allow: ["domain", "contracts", "shared"] },
// app は「app / domain / contracts / shared」だけOK(adapters禁止)
{ from: "app", allow: ["app", "domain", "contracts", "shared"] },
// adapters は「なんでも使ってOK」寄り(外側なので)
{ from: "adapters", allow: ["adapters", "app", "domain", "contracts", "shared"] },
// contracts/shared は広めに許可(ただし“業務ルール”は置かない前提)
{ from: "contracts", allow: ["contracts", "shared"] },
{ from: "shared", allow: ["shared"] },
],
},
],
// ✅ 循環参照を検出(import/no-cycle)
"import/no-cycle": ["error", { maxDepth: 1 }],
// ✅ おまけ:importの順序を整える系は好みで追加してね✨
},
}
);
この境界設定の考え方は eslint-plugin-boundaries の基本に沿ってるよ🧱(JS Boundaries)
import/no-cycle は eslint-plugin-import のルールだよ🌀(GitHub)
16-4-2 動作確認(わざと違反してみる😈➡️✅)
例えば src/domain/todo.ts から src/adapters/db.ts を import してみてね👇
(※この「悪い例」をやるのは今だけね!笑)
// src/domain/todo.ts
import { db } from "../adapters/db"; // ❌ これは境界違反になる想定
export const x = 1;
そして lint 実行👇
npx eslint .
✅ ここで “境界違反エラー”が出たら勝ち🎉🛡️ (出ない場合は、パスやフォルダ名が違ってる可能性が高いよ)
16-5 「設定できてるか不安…」を救う:Config Inspector🔍🧁
flat configって、設定が合ってるか迷子になりやすいの🥺 そんな時は ESLint Config Inspector が超助かるよ✨
npx eslint --inspect-config
または ESLintが入ってなくても👇
npx @eslint/config-inspector
ブラウザで設定の“最終結果”を見れるよ👀💓(ESLint)
16-6 依存を“見える化”する📈🗺️(dependency-cruiser / madge)
16-6-1 dependency-cruiserで健康診断🚢🩺
dependency-cruiser は「循環参照」「依存の向き」「孤立ファイル」みたいな 構造チェックに強いよ💪(npm)
まずは設定ファイルを用意(例:.dependency-cruiser.cjs)
module.exports = {
forbidden: [
{
name: "no-domain-to-adapters",
severity: "error",
from: { path: "^src/domain" },
to: { path: "^src/adapters" },
},
{
name: "no-app-to-adapters",
severity: "error",
from: { path: "^src/app" },
to: { path: "^src/adapters" },
},
{
name: "no-circular",
severity: "error",
from: {},
to: { circular: true },
},
],
};
実行👇
npx depcruise --validate .dependency-cruiser.cjs src
✅ これで 境界違反や循環があったら落ちる ようになるよ🏁
16-6-2 グラフを“画像化”する🖼️✨(Graphviz)
Graphviz は winget で入れられるよ📦(Graphviz)
winget install graphviz
そして dot を吐く👇
npx depcruise -T dot src > docs/deps.dot
SVGに変換👇
dot -Tsvg docs/deps.dot -o docs/deps.svg
docs/deps.svg を開けば、依存が“絵”になるよ〜!📈😍
16-6-3 madgeで循環参照をサクッと検出🕸️🌀
madge は「循環ある?」を即見たい時に便利✨(GitHub)
npx madge --circular --extensions ts,tsx src
(tsconfig を使ってパス解決したいなら --ts-config を足すのもアリ👌)
16-7 package.json に “習慣コマンド”を作る🧠✅
毎回 npx ... するの面倒だから、scripts化しよっ☺️
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"deps:check": "depcruise --validate .dependency-cruiser.cjs src",
"deps:graph": "depcruise -T dot src > docs/deps.dot && dot -Tsvg docs/deps.dot -o docs/deps.svg",
"quality": "npm run lint && npm run deps:check"
}
}
これで👇が定番になる✨
npm run quality
16-8 CIに入れて“未来の自分”を助ける🤖🛟
GitHub Actions(例:.github/workflows/quality.yml)
name: quality
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npm run quality
✅ これで「境界違反のPR」は自動で止まるよ🛡️✨
16-9 PRテンプレ(レビュー観点)💌✅
.github/pull_request_template.md に置いちゃおう📌
## 変更内容📝
-
## 依存関係ルールチェック✅
- [ ] domain が adapters を import してない
- [ ] app が adapters を import してない
- [ ] 変換/例外翻訳は adapters 側に置けてる
- [ ] 循環参照が増えてない(npm run deps:check)
- [ ] eslint が通る(npm run lint)
## 補足💬
-
16-10 AIの使いどころ🤖🪄(この章は相性いい!)
境界ルール作りをAIに手伝ってもらう🧱
- 「このフォルダ構成に合わせて boundaries の element-types ルール案を作って。domain→adaptersは禁止で」
- 「domain/app/adapters/contracts/shared の依存許可表を作って」
依存グラフの読み解きをAIに頼む📈
- 「deps.svg を見て、循環しそうな塊を3つ指摘して、切り方の案を出して」
- 「この依存の塊を“より内側に寄せる”リファクタ案を提案して」
ルール違反例を教材用に作る📚
- 「あえて domain→adapters import を作る悪い例と、直した良い例を作って(理由つき)」
16-11 よくある詰まりポイント集🧯😵💫
❓ boundaries が効かない
boundaries/includeがsrc/**/*になってる?boundaries/elementsのpatternが実フォルダと一致してる?- Config Inspector で「そのファイルにルールが当たってるか」確認しよ🔍(ESLint)
❓ import/no-cycle が重い
import/no-cycle はプロジェクト規模が大きいと重くなりやすい話があるよ🥲
- まず
maxDepth: 1みたいに軽めから - きつい時は dependency-cruiser / madge 側で循環チェックを強めるのもアリ🚢🕸️(GitHub)
まとめ🏁✨(この章で“完成”したこと)
- ✅ 境界違反を ESLintで自動ブロック🛡️
- ✅ 循環参照を ルールで検出🌀
- ✅ 依存を グラフで可視化📈
- ✅ CIで 壊れる前に止める🤖
- ✅ PRテンプレで レビューが楽になる💌
必要なら、今あるあなたのミニプロジェクトのフォルダ構成(src配下だけでOK)を貼ってくれたら、その構成に“ぴったりフィット”する boundaries ルールに調整した完成版を作るよ😊🧩✨