メインコンテンツまでスキップ

第16章 ルールを自動で守る:ESLint境界ルール+依存の見える化🛡️📈🏁

ここまでで「依存の向き(外→内)を守るのが大事!」って分かってきたと思うんだけど… 人の注意力だけで守り続けるのはムリゲーになりがち🥲💦

だから第16章はこうするよ👇

  • ESLintで“境界ルール”を強制して、うっかり違反を防ぐ🛡️
  • ✅ **依存グラフを“見える化”**して、構造の健康診断をする📈
  • CIに入れて未来の自分を救う🤖🏥

16-0 今日のゴール🎯✨

「お願い」じゃなくて「仕組み」で守る状態にするよ💪😊 例:

  • src/domainsrc/adaptersimport したら 即エラー🚫
  • 循環参照(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)
  • Graphvizdotファイルを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-cycleeslint-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/includesrc/**/* になってる?
  • boundaries/elementspattern が実フォルダと一致してる?
  • Config Inspector で「そのファイルにルールが当たってるか」確認しよ🔍(ESLint)

❓ import/no-cycle が重い

import/no-cycle はプロジェクト規模が大きいと重くなりやすい話があるよ🥲

  • まず maxDepth: 1 みたいに軽めから
  • きつい時は dependency-cruiser / madge 側で循環チェックを強めるのもアリ🚢🕸️(GitHub)

まとめ🏁✨(この章で“完成”したこと)

  • ✅ 境界違反を ESLintで自動ブロック🛡️
  • ✅ 循環参照を ルールで検出🌀
  • ✅ 依存を グラフで可視化📈
  • ✅ CIで 壊れる前に止める🤖
  • ✅ PRテンプレで レビューが楽になる💌

必要なら、今あるあなたのミニプロジェクトのフォルダ構成(src配下だけでOK)を貼ってくれたら、その構成に“ぴったりフィット”する boundaries ルールに調整した完成版を作るよ😊🧩✨