🗂

dependency-cruiser-report-actionでPRの変更ファイルの依存関係を可視化してコメントする

2022/06/27に公開約5,500字

この記事は、筆者が制作したGitHub Actions向けCustom actionであるdependency-cruiser-report-actionを紹介する記事です。


JavaScript / TypeScriptのプログラムではexportによりモジュールとして分割しimport(require) で読み込むことができますが、一度exportで公開してしまうとプロジェクト内のどこからでも読み込むことができてしまいます。
無秩序にimportを増やして依存関係が複雑になるとモジュール間は密結合になります。1つの小さな変更が大規模な障害に発展したり、変更をリリースするまでのリードタイムは伸びていくなどの悪循環に陥ります。

プロダクトを安全にメンテナンスし続けるためにはこの「依存」と立ち向かうことになります。
立ち向かうためのアプローチとしてはフレームワークによる規約の利用、SOLIDのような設計原則、いくつかのレイヤーを定義したアーキテクチャパターンの活用などが挙げられ、より具体的な方法ではESLintプラグインなどでCIとして制約を実装している例も見かけます。

https://zenn.dev/uhyo/articles/eslint-plugin-import-access

https://github.com/knowledge-work/eslint-plugin-strict-dependencies

その中で、 JavaScript / TypeScrptプロジェクトの依存を検査・可視化し、1枚の画像に起こしてくれるdependency-cruiserというCLIツールがあります。

https://github.com/sverweij/dependency-cruiser

「依存」に対して思考や議論を深めながらチームでの開発を進める上で、Pull Requestの変更差分に対して依存関係を可視化できれば、より俯瞰的に・より有意義なレビュープロセスを進められるかもしれません。それをdependency-cruiser-report-actionが実現します。

demo

dependency-cruiser-report-actionはGitHub Actionsとして実行します。グラフ化した依存関係をMermaid.jsシンタックスのテキストとして出力し、それをPull Requestのコメントとして追加します。

dependency-cruiser-report-action自体もTypeScriptで実装されているため開発中に利用しています。このような表示が可能です。

実際のPull Request: https://github.com/MH4GF/dependency-cruiser-report-action/pull/80

緑にハイライトされているファイル名が該当のPull Requestで変更があったファイルです。
dependency-cruiserはファイルが依存するモジュールを再帰的に収集し列挙するため、Pull Requestのレビュアーは変更差分ファイルだけでなく周辺のコードの関係も認識できます。

出力結果はMermaid.jsシンタックスのテキストなため、 NotionやZenn等に貼り付けることもできます。

出力されるテキスト
flowchart LR

subgraph 0["src"]
1["ActionError.ts"]
2["main.ts"]
3["options.ts"]
subgraph 4["options"]
5["validateOptions.ts"]
9["validateOptions.test.ts"]
d["filterSupportedFiles.ts"]
e["formatFocusOption.ts"]
end
subgraph 6["report"]
subgraph 7["body"]
8["reportBody.ts"]
a["reportBody.test.ts"]
f["uniqueTag.ts"]
end
b["generateReport.ts"]
end
c["installDependencies.ts"]
g["runDepcruise.ts"]
end
2-->1
2-->c
2-->3
2-->b
2-->g
3-->d
3-->e
3-->5
5-->1
8-->f
9-->1
9-->5
a-->8
b-->8

style 1 fill:lime,color:black
style 2 fill:lime,color:black
style 3 fill:lime,color:black
style 5 fill:lime,color:black
style 8 fill:lime,color:black
style 9 fill:lime,color:black
style a fill:lime,color:black

技術的な余談

ご覧いただくと分かる通り、dependency-cruiserが出力するテキストでは、Mermaid.jsのnode(上記の 0["src"] での0)としてはファイルパスではなく16進数の文字列に置き換えています。

これは2つ理由があります。1つ目は、ファイルパスとして正しい文字列であってもMermaid.jsでメタ文字として扱われてしまい、シンタックスエラーになるパターンが多いという問題があります。(例: foo/bar--baz.js では /-- がシンタックスエラーになってしまう)

2つ目はMermaid.jsの文字量(バイト数)制限です。デフォルトでは5万文字以上のテキストがMermaid.jsに渡された場合制限に到達し構文解析が行われなくなります。
これはMermaid.jsの設定で変えることはできますが、設定を変えることができるのはGitHubやZenn、Notionなどのサービス提供側で、大抵は5万文字に設定されています。

16進数の文字列を使うことで、メタ文字として解釈されないかつ比較的小さいバイト数でそれぞれのnodeのユニーク性を担保できます。

dependency-cruiserではこの設定をデフォルトとしています。設定ファイルである .dependency-cruiser.jsで minify: false とすることで人間にとって可読性の高いテキストの出力も可能です。
https://github.com/sverweij/dependency-cruiser/blob/develop/doc/options-reference.md#mermaid

グラフのレンダリングツールとしてはGraphVizなどが有名ですが、それらはSVGやPNGとして出力することになります。 GitHubのAPIではコメント等でのファイルの添付はできないため、CIとして画像を扱いたい場合はS3やGoogle Cloud Storageなどにアップロードする必要がありました。[1]

しかしブラウザで動作する軽量なレンダリングツールであるMermaid.jsがGitHubなど複数のサービスで採用されたことで今回のアプローチを採ることが出来ました。

利用方法

続いて実プロジェクトへの導入方法を紹介します。READMEにも記載していますが、補足も交えて紹介していきます。

dependency-cruiserをプロジェクトローカルにインストール

以下のコマンドでプロジェクトローカルにインストールしてください。

npm install --save-dev dependency-cruiser

# or
yarn add --dev  dependency-cruiser
npxではダメなのか?

dependency-cruiserはnpxでも実行可能ですが、正しく依存関係を識別できない可能性があります。プロジェクトローカルにインストールすることを推奨します。
dependency-cruiserにはTypeScript / Vue / CoffeeScript / LiveScriptなどを処理するためのトランスパイラが付属しておらず、環境で利用可能なものを使用するためです。
ref: https://github.com/sverweij/dependency-cruiser/blob/develop/doc/faq.md#q-typescript-coffeescript-livescript-or-vue-single-file-component-sfc-dependencies-dont-show-up-how-can-i-fix-that

dependency-cruiserの設定ファイルを対話形式で作成

以下のコマンドを実行することで対話形式に設定ファイルを作成できます。プロジェクトでtsconfig.jsonや .babelrcなどを利用している場合はここでファイルパスを入力してください。

npm run depcruise --init

詳しいオプションを確認したい場合は公式のドキュメントをご覧ください。
https://github.com/sverweij/dependency-cruiser/blob/develop/doc/options-reference.md

筆者のNext.js製の別プロジェクトの設定も参考までに添付します。
https://github.com/MH4GF/log.mh4gf.dev/blob/main/.dependency-cruiser.js

.github/workflows配下にワークフローを追加

以下のようなワークフローを追加してください。

name: 'depcruise'
on:
  pull_request:

jobs:
  report:
    permissions:
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: MH4GF/dependency-cruiser-report-action@v0.1.2

これで完了です。 Pull Requestに新たなコミットを追加するとコメントとして図が追加されます。

まとめ

本記事ではPull Requestごとに変更差分ファイルの依存を可視化しコメントとして追加するdependency-cruiser-report-actionを紹介しました。

差分ファイルに対して局所的に可視化するため、大小問わずJS/TSプロジェクトで活用できるはずです。もちろんローカルでdependency-cruiserを実行しプロジェクト全体の依存を確認することも有益かと思います。是非使ってみてください。リポジトリへのStarもお待ちしています。

脚注
  1. CIで画像を扱う別のツールとしてはreg-suitやlighthouse-ciがそのアプローチを採っています。GitHub の Artifacts を使う手もありますが、現状アップロードしたファイルのホスティングができないため Actions の Summary に遷移しダウンロードする必要があり、開発中に継続的に閲覧するには少々使い勝手が悪いです。 ↩︎

GitHubで編集を提案

Discussion

ログインするとコメントできます