前端模塊依賴複雜度太高,一團亂麻了怎麼辦
隨着項目規模龐大,文件層級與結構的複雜度越來越高,模塊關係混亂,循環依賴,反向依賴行爲越來越多。
爲了保持項目穩定和架構良好,需要進行模塊依賴關係治理。
dependency-cruiser
在這方面是個很棒的工具,今天主要介紹下它的應用:
-
dependency-cruiser 介紹
-
依賴關係可視化
-
依賴關係校驗
dependency-cruiser 介紹
Github 倉庫:https://github.com/sverweij/dependency-cruiser
它支持前端常用的 JavaScript,TypeScript 語言和 ESM,CommonJS 等模塊規範。
在項目裏通常與 ESLint 配套使用,一個用於代碼檢查,一個用於依賴檢查。
依賴關係可視化
可視化依賴關係能夠幫助你更快地瞭解和洞察一個項目,下圖是使用 dependency-cruiser 生成的 preact 依賴關係圖。
使用方式
- 依賴安裝
首先安裝 Graphviz,用來生成依賴關係圖。
brew install graphviz
接着在項目裏安裝 dependency-cruiser
。
npm i dependency-cruiser -D
- 生成圖片
安裝好就可以直接執行命令使用了:
npx depcruise —-output-type dot src | dot -T svg > dependency-graph.svg
執行後會在根目錄下得到一張 svg 格式的依賴關係圖。
上方的核心命令是 depcruise src
,表示對 src 下的文件進行依賴追蹤。
其餘參數用來控制輸出格式:
-
—-output-type dot
表示輸出格式爲dot
,意味着使用 Graphviz 來輸出。 -
dot -T svg > dependency-graph.svg
爲 Graphviz 的命令行語法,表示輸出名爲 dependency-graph 的 svg 文件。
建議將該命令放在 package.json 的 npm 腳本中,還能配合 CI/CD 完成依賴圖生成自動化。
其它參數
除了控制輸出格式,我們還能通過一些參數對依賴圖進行控制。
-
—-exclude
:用於過濾掉圖上不關心的依賴。 -
--include-only
:與--exclude
相反,只保留範圍內的依賴。 -
-—do-not-follow
:會過濾掉某個依賴的後續依賴。 -
—-max-depth
:指定依賴樹的深度。將依賴圖的輸出大小保持在可控範圍內。
比如設置 --max-depth 1
後生成的 preact 依賴圖爲:
更多案例可以參考官方文檔 Real world samples[1]。
VS Code 插件
除了命令,使用 VSCode 插件 Dependency Cruiser Extension[2] 也能快速查看依賴圖。
依賴關係校驗
dependency-cruiser 也可以像 ESLint 一樣自定義規則來對依賴關係進行校驗。
它能夠前置規避掉可能出現的各種依賴關係問題,使用起來也非常簡單。
使用方式
- 依賴安裝
npm i dependency-cruiser -D
- 初始化配置文件
npx depcruise -—init
根據命令行提示完成操作。
.dependency-cruiser.js
配置文件。
- 被禁止的依賴用法規則列表,放在
forbidden
字段下。
規則列表由一個個規則項組成,自動生成後會內置一些推薦規則。
- 其它配置,放在
options
字段下
包括依賴追蹤範圍,模塊規範,TS、Webpack config 文件路徑等,也是自動生成的,通常不需要改動。
簡化後的整體結構如下:
// .dependency-cruiser.js
module.exports = {
forbidden: [
{...}, // 規則1
{...}, // 規則2
{...}, // 規則3
// ...
],
options: {
// ...
}
}
排除掉不太需要關心的配置後,是不是看起來更清晰了?
後文會詳細說明規則項如何配置。
- 校驗依賴
執行命令
npx depcruise src --config .dependency-cruiser.js
依賴關係校驗未通過會像 ESLint 一樣拋出異常。
建議將命令放在 package.json 中的 npm scripts 中,並結合 git hook 或 CI 設置卡點。
內置規則
默認配置文件爲我們內置了一些推薦規則,一起來看看。
「禁止循環引用」規則
循環引用指的是,模塊 A 依賴模塊 B,而模塊 B 又依賴模塊 A。
// moduleA.ts
import moduleB from './moduleB';
export default function moduleA() {
return 'moduleA';
}
moduleB();
// moduleB.ts
import moduleA from './moduleA';
export default function moduleB() {
return 'moduleB';
}
moduleA();
如果處理地不好,可能導致程序異常。依賴關係複雜的大項目難免會出現這樣的情況。
禁止循環引用規則就是用來檢測這種場景的,規則配置如下:
{
// ----- 規則基本配置 -----
name: 'no-circular', // 規則名稱
severity: 'error', // 嚴重等級
comment: '禁止循環引用', // 規則描述
// ----- 規則內容 -----
from: {}, // 不填則表示所有引用
to: {
"circular": true, // 不允許成環
},
},
name
,severity
和 comment
用於描述規則的基本信息。
from
和 to
描述規則的具體內容,from 表示「依賴方」,to 表示「被依賴方」。
上方的配置表示:任何依賴引用,只要成環,就會報錯 error(默認等級是 warn,這裏改成 error 用於演示)。
測試一下上方 moduleA 和 B 循環依賴的例子,執行校驗命令會在控制檯看到報錯,阻塞後續流程。
其他內置規則
內置規則除了「禁止循環引用」,還有:
not-to-unresolvable
:禁止引用不存在的模塊,這會導致程序出錯。
no-orphans
:檢測未被使用的模塊,提醒我們及時進行代碼清理,避免干擾。
not-to-dev-dep
:禁止生產環境代碼使用開發依賴,這在開發 node 應用或者 npm 包時可能會存在問題。
自定義規則
我們也可以根據項目場景自定義規則。
「禁止跨模塊引用」規則
兩個非相關的模塊如果出現跨模塊引用會導致強耦合。
比如 pageA 和 pageB 是兩個獨立的頁面組件,但 pageA 下面的 component 直接引用了 pageB 的 utils。
// pageA/component.tsx
// 引用了 pageB 的模塊
import utils from '../pageB/utils';
export default function component() {
// ...
return 'page A 下的組件';
}
這樣會導致兩個頁面糾纏不清,應該儘量避免。
正確的做法應該是將用到公共模塊放到更高的層級 src/utils
,頁面從公共模塊中引入。
但實際開發中這也是很難避免的,我們可以基於此場景配置一條規則:「禁止 pageA 引入其它頁面模塊」。
{
// ----- 規則基本配置 -----
name: 'no-cross-module-import',
severity: 'error', // 嚴重等級
comment: '禁止跨模塊引用',
// ----- 規則內容 -----
from: {
path: '^src/pageA',
},
to: {
pathNot: [
// 只能引入自己或公共的模塊
'^src/pageA',
'^src/utils',
]
},
},
任何 pageA 的依賴引用,只要不是來自自身或者全局 utils,就會報錯 error。
測試一下,執行校驗命令會在控制檯看到報錯。
其它自定義規則
這裏再簡單介紹兩個自定義的規則。
- 「禁止直接引用某個模塊」
如果項目中有對 axios 進行封裝,業務開發時應該使用封裝後的請求庫。
爲了避免直接使用 axios,可以配置一條「禁止直接引用 axios」規則。
{
// ----- 規則基本配置 -----
name: 'not-direct-axios',
comment: '禁止直接使用 axios',
severity: 'error',
// ----- 規則內容 -----
from: {}, // 所有引用
to: {
path: 'axios',
},
},
- 「葉子依賴禁止再依賴其它模塊」
還是封裝的場景,比如項目內基於瀏覽器的 cookie api 封裝了一個 cookie 庫。
cookie 庫只依賴 cookie api,不依賴其它模塊,可以配置一條規則來「強制它是一個葉子依賴」。
{
// ----- 規則基本配置 -----
name: 'cookies-leaf',
comment: 'cookies 庫不應該有其它依賴',
severity: 'error',
// ----- 規則內容 -----
from: {
path: '^src/lib/cookies',
},
to: {}, // 不能引用任何其它依賴
},
其它更多場景,歡迎留言討論交流。
總結
本文我們介紹了 dependency-cruiser 治理項目模塊依賴關係的兩種使用方式。
-
依賴關係可視化:使用命令可以生成和控制輸出的依賴關係圖。對於追蹤單個文件的依賴關係的場景,使用 VS Code 插件會更方便。
-
依賴關係校驗:可以像 ESLint 一樣通過命令來校驗依賴關係,而且支持自定義規則,比如「禁止循環依賴」,「禁止跨模塊引用」等。
當然,這僅僅是它能力的冰山一角,可以去 Github 主頁 [3] 瞭解更多高級用法。
參考資料
[1]
Real world samples: https://github.com/sverweij/dependency-cruiser/blob/develop/doc/real-world-samples.md
[2]
Dependency Cruiser Extension: https://marketplace.visualstudio.com/items?itemName=juanallo.vscode-dependency-cruiser
[3]
Github 主頁: https://github.com/sverweij/dependency-cruiser
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ZYW2rqZuRIRs-pfoUunQLA