目次
- 背景 🖼️
- 使い方 🚧
2.1 install
2.2 configファイル作成
2.3 依存関係グラフ作成
2.4 依存関係の検証 - 実際にコマンド叩いてみる 🧪
3.1 install
3.2 configファイル作成
3.3 依存関係グラフ作成
3.4 依存関係の検証 - ユースケース 💡
4.1 github-actionsで循環依存関係になっているものを検出して知らせる
4.2 package private のような設計を作る - 最後に 🚢
- 参考 ✨
1. 背景 🖼️
-
こちらのイベントでdependency-cruiserという言葉を聞いて気になったからです。
-
業務でTypeScriptのプロジェクトを担当してます。
exportするとプロジェクト全体にexportされてしまうため、依存関係を保護し続けるのは難しいです。そのため、思いも寄らぬ依存関係が発じたり、循環参照が発生したりする恐れがあります。
dependency-cuiserはそんな時に有効だと知りました。
1. dependency-cruiserとは
https://raw.githubusercontent.com/sverweij/dependency-cruiser/main/doc/assets/sample-dot-output.png
これは、JavaScript、TypeScript、LiveScript、またはCoffeeScriptプロジェクトの依存関係を調べ、次のようにします。
- 自分のルールと照らし合わせて検証する
- テキスト、グラフィックででルール違反を報告する
以下はreactの依存関係グラフです。
2. 使い方 🚧
2.1 install
npm install --save-dev dependency-cruiser
# or
yarn add -D dependency-cruiser
pnpm add -D dependency-cruiser
2.2 configファイル作成
npx depcruise --init
環境を少し調べ、いくつか質問をし
プロジェクトに適応した dependency-cruiser.js 設定ファイルを作成するそうです
- 循環する依存関係
- package.jsonで見つからない依存関係
- 孤児
- dev-またはoptionalDependenciesに依存するプロダクションコードの検出
等、ほとんどのプロジェクトで意味のあるルールを追加してくれます
2.3 依存関係グラフ作成
graphvizをinstallしてください
brew install graphviz
例)src ディレクトリ内の依存関係のグラフを作成する
npx depcruise src --include-only "^src" --output-type dot | dot -T svg > dependency-graph.svg
2.4 依存関係の検証
例)src ディレクトリ
npx depcruise src
ルールと照らし合わせて検証し、違反があればESLintのような形式で表示します。
https://raw.githubusercontent.com/sverweij/dependency-cruiser/main/doc/assets/sample-err-output.png
3. 実際にコマンド叩いてみる 🧪
sampleリポジトリで動かしながら進めていきます。
3.1 install
# npm i --save-dev dependency-cruiser
3.2 configファイル作成
# npm npx depcruise --init
以下のようなことを聞かれました
# npx depcruise --init
✔ Where do your source files live? … src
✔ Do your test files live in a separate folder? … no
✔ Looks like you're using a 'tsconfig.json'. Use that? … yes
✔ Full path to your 'tsconfig.json › tsconfig.json
✔ Also regard TypeScript dependencies that exist only before compilation? … yes
✔ Looks like you're using webpack - specify a webpack config? … yes
✔ Successfully created '.dependency-cruiser.js'
srcディレクトリと同じ階層にconfigファイル(.dependency-cruiser.js)が作成されました。
3.3 依存関係グラフ作成
graphvizを使えるようにinstallします。
FROM node:lts-bullseye
RUN apt-get update && apt-get install -y graphviz
WORKDIR /app
docker-compose buildして以下を実行します。
# npx depcruise src --output-type dot | dot -T svg > output.svg
github上でsvgファイルを見ると以下のようになりました。
3.4 依存関係の検証
# npx depcruise src
✔ no dependency violations found (2 modules, 1 dependencies cruised)
依存関係の違反は見つかりませんでした。
srcディレクトリ配下にはファイルが2つ、依存関係が1つあります。
とのことでした。
依存関係の違反を起こしてみます(循環依存)
import { sub } from "./calc";
console.log(sub(1, 1));
export const violation = "violation";
import { violation } from "./index";
export function sub(a: number, b: number) {
console.log(violation);
return a - b;
}
# npx depcruise src
warn no-circular: src/calc.ts →
src/index.ts →
src/calc.ts
✘ 1 dependency violations (0 errors, 1 warnings). 2 modules, 2 dependencies cruised.
実際に処理を実行すると、violationの値はundefinedで表示されてます。
# node dist/content_scripts.js
undefined
0
エラーになるケースや、ならないケースはありますが、意図しない挙動をするということを確認できました。
依存関係の違反(循環依存)をwarnからerrorに変更し、validateします。
severity: 'warn',をseverity: 'error'にします。
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
forbidden: [
{
name: 'no-circular',
severity: 'error',
comment:
'This dependency is part of a circular relationship. You might want to revise ' +
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
from: {},
to: {
circular: true
}
},
この設定でvalidateします。
errorとして表示されました。
# npx depcruise src
error no-circular: src/calc.ts →
src/index.ts →
src/calc.ts
✘ 1 dependency violations (1 errors, 0 warnings). 2 modules, 2 dependencies cruised.
4. ユースケース 💡
4.1 github-actionsで循環依存関係になっているものを検出して知らせる
name: Dependency Test CI
on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: cd app && npm ci
- name: Run dependency test
run: cd app && npx depcruise src
4.2 package private のような設計を作る
内部パッケージのような挙動を実現するルールを作成できます。
- _から始まるディレクトリ内部のファイルは、同ディレクトリ内のファイルまたは、直上のディレクトリのファイルからのみimport可能
- _から始まるファイルは同階層に置かれたファイルからのみimport可能
fromがimport(require)する側で、toがexportする側です。
pathがルールを適用するファイルパスで、pathNotが適用されないファイルパスです。
module.exports = {
forbidden: [
{
name: `1. '_'から始まるディレクトリ内部のファイルは、同ディレクトリ内のファイルまたは、一つ上の階層のディレクトリのファイルからのみimport可能`,
severity: 'error',
from: { path: ['(.*)\\/.*\\.ts'], pathNot: ['.*\\.spec\\.ts$'] },
to: {
path: ['_\\w+\\/\\w+\\.ts$'],
pathNot: ['$1\\/_\\w+\\/\\w+\\.ts$', '$1\\/\\w+\\.ts$'],
},
},
{
name: `2. '_'から始まるファイルは同階層に置かれたファイルからのみimport可能`,
severity: 'error',
from: { path: ['(.*)\\/.*\\.ts$'], pathNot: ['.*\\.spec\\.ts$'] },
to: {
path: ['.*\\/_\\w+.ts$'],
pathNot: ['$1\\/_\\w+.ts$'],
},
},
]
例で以下のフォルダ構成で解説します。
src/
├── _package1/
│ ├── _package2/
│ │ ├── _private.ts
│ │ └──calc.ts
│ └── importer.ts
├── index.ts
└── exporter.ts
# npx depcruise src
error 2. '_'から始まるファイルは同階層に置かれたファイルからのみimport可能: src/_package1/importer.ts → src/_package1/_package2/_private.ts
error 1. '_'から始まるディレクトリ内部のファイルは、同ディレクトリ内のファイルまたは、一つ上の階層のディレクトリのファイルからのみimport可能: src/index.ts → src/_package1/_package2/calc.ts
✘ 2 dependency violations (2 errors, 0 warnings). 5 modules, 5 dependencies cruised.
以下画像が依存関係グラフで赤字がエラーです。
5. 最後に 🚢
- 循環依存関係を自動的に検出してくれるのはありがたいなと思いました!!
中々原因が掴めず苦労したことが過去ありましたので。。 - private pacakgeな設計ができるので心置きなくモジュール管理できるようになるのも魅力的です。
6. 参考 ✨
- メルカリ Shops の開発を支える Automation 化
- ESLintだけでは守れない。Dependency cruiserによるアーキテクチャー保護
- dependency-cruiser-report-actionでPRの変更ファイルの依存関係を可視化してコメントする
他にも依存関係の管理に関する記事を書いています 💪