全文 2500 字,閱讀時長約 30 分鐘。若是以爲文章有用,歡迎點贊關注,但寫做實屬不易,未經做者贊成,禁止任何形式轉載!!!
Dependency Graph 概念來自官網 Dependency Graph | webpack 一文,原文解釋是這樣的:javascript
Any time one file depends on another, webpack treats this as a dependency_. This allows webpack to take non-code assets, such as images or web fonts, and also provide them as _dependencies for your application.java
When webpack processes your application, it starts from a list of modules defined on the command line or in its configuration file. Starting from these entry points_, webpack recursively builds a _dependency graph that includes every module your application needs, then bundles all of those modules into a small number of bundles - often, just one - to be loaded by the browser.webpack
翻譯過來核心意思是:webpack 處理應用代碼時,會從開發者提供的 entry 開始遞歸地組建起包含全部模塊的 dependency graph _,_以後再將這些 module 打包爲 bundles 。web
然而事實遠不止官網描述的這麼簡單,Dependency Graph 貫穿 webpack 整個運行週期,從 make 階段的模塊解析,到 seal 階段的 chunk 生成,以及 tree-shaking 功能都高度依賴於Dependency Graph ,是 webpack 資源構建的一個很是核心的數據結構。算法
本文將圍繞 webpack\@v5.x 的 Dependency Graph 實現,展開討論三個方面的內容:數據結構
學習本文,您將進一步瞭解 webpack 模塊解析的處理細節,結合前文 [萬字總結] 一文吃透 Webpack 核心原理 ,您能夠更透徹地瞭解 webpack 的核心機制。架構
關注公衆號【Tecvan】,回覆【1】,獲取 Webpack 知識體系腦圖
本節將深刻 webpack 源碼,解讀 Dependency Graph 的內在數據結構及依賴關係收集過程。在正式展開以前,有必要回顧幾個 webpack 重要的概念:app
Module
:資源在 webpack 內部的映射對象,包含了資源的路徑、上下文、依賴、內容等信息Dependency
:在模塊中引用其它模塊,例如 import "a.js"
語句,webpack 會先將引用關係表述爲 Dependency 子類並關聯 module 對象,等到當前 module 內容都解析完畢以後,啓動下次循環開始將 Dependency 對象轉換爲適當的 Module 子類。Chunk
:用於組織輸出結構的對象,webpack 分析完全部模塊資源的內容,構建出完整的 Dependency Graph 以後,會根據用戶配置及 Dependency Graph 內容構建出一個或多個 chunk 實例,每一個 chunk 與最終輸出的文件大體上是一一對應的。Webpack 4.x 的 Dependency Graph 實現較簡單,主要由 Dependence/Module 內置的系列屬性記錄引用、被引用關係。ide
而 Webpack 5.0 以後則實現了一套相對複雜的類結構記錄模塊間依賴關係,將模塊依賴相關的邏輯從 Dependence/Module 解耦爲一套獨立的類型結構,主要類型有:函數
ModuleGraph
:記錄 Dependency Graph 信息的容器,一方面保存了構建過程當中涉及到的全部 module
、dependency
對象,以及這些對象互相之間的引用;另外一方面提供了各類工具方法,方便使用者迅速讀取出 module
或 dependency
附加的信息ModuleGraphConnection
:記錄模塊間引用關係的數據結構,內部經過 originModule
屬性記錄引用關係中的父模塊,經過 module
屬性記錄子模塊。此外還提供了一系列函數工具用於判斷對應的引用關係的有效性ModuleGraphModule
:Module
對象在 Dependency Graph 體系下的補充信息,包含模塊對象的 incomingConnections
—— 指向模塊自己的 ModuleGraphConnection 集合,即誰引用了模塊本身;outgoingConnections
—— 該模塊對外的依賴,即該模塊引用了其餘那些模塊。類間關係大體爲:
上面類圖須要額外注意:
ModuleGraph
對象經過 _dependencyMap
屬性記錄 Dependency
對象與 ModuleGraphConnection
鏈接對象之間的映射關係,後續的處理中能夠基於這層映射迅速找到 Dependency
實例對應的引用與被引用者ModuleGraph
對象經過 _moduleMap
在 module
基礎上附加 ModuleGraphModule
信息,而 ModuleGraphModule
最大的做用就是記錄了模塊的引用與被引用關係,後續的處理能夠基於該屬性找到 module
實例的全部依賴與被依賴關係ModuleGraph
、ModuleGraphConnection
、ModuleGraphModule
三者協做,在 webpack 構建過程(make 階段)中逐步收集模塊間的依賴關係,回顧前文 [萬字總結] 一文吃透 Webpack 核心原理 說起的構建流程圖:
構建流程自己很複雜,建議讀者對比閱讀 [萬字總結] 一文吃透 Webpack 核心原理 一文,加深理解。依賴關係收集過程主要發生在兩個節點:
addDependency
:webpack 從模塊內容中解析出引用關係後,建立適當的 Dependency
子類並調用該方法記錄到 module
實例handleModuleCreation
:模塊解析完畢後,webpack 遍歷父模塊的依賴集合,調用該方法建立 Dependency
對應的子模塊對象,以後調用 compilation.moduleGraph.setResolvedModule
方法將父子引用信息記錄到 moduleGraph
對象上setResolvedModule
方法的邏輯大體爲:
class ModuleGraph { constructor() { /** @type {Map<Dependency, ModuleGraphConnection>} */ this._dependencyMap = new Map(); /** @type {Map<Module, ModuleGraphModule>} */ this._moduleMap = new Map(); } /** * @param {Module} originModule the referencing module * @param {Dependency} dependency the referencing dependency * @param {Module} module the referenced module * @returns {void} */ setResolvedModule(originModule, dependency, module) { const connection = new ModuleGraphConnection( originModule, dependency, module, undefined, dependency.weak, dependency.getCondition(this) ); this._dependencyMap.set(dependency, connection); const connections = this._getModuleGraphModule(module).incomingConnections; connections.add(connection); const mgm = this._getModuleGraphModule(originModule); if (mgm.outgoingConnections === undefined) { mgm.outgoingConnections = new Set(); } mgm.outgoingConnections.add(connection); } }
上例代碼主要更改了 _dependencyMap
及 moduleGraphModule
的出入 connections
屬性,以此收集當前模塊的上下游依賴關係。
看個簡單例子,對於下面的依賴關係:
Webpack 啓動後,在構建階段遞歸調用 compilation.handleModuleCreation
函數,逐步補齊 Dependency Graph 結構,最終可能生成以下數據結果:
ModuleGraph: { _dependencyMap: Map(3){ { EntryDependency{request: "./src/index.js"} => ModuleGraphConnection{ module: NormalModule{request: "./src/index.js"}, // 入口模塊沒有引用者,故設置爲 null originModule: null } }, { HarmonyImportSideEffectDependency{request: "./src/a.js"} => ModuleGraphConnection{ module: NormalModule{request: "./src/a.js"}, originModule: NormalModule{request: "./src/index.js"} } }, { HarmonyImportSideEffectDependency{request: "./src/a.js"} => ModuleGraphConnection{ module: NormalModule{request: "./src/b.js"}, originModule: NormalModule{request: "./src/index.js"} } } }, _moduleMap: Map(3){ NormalModule{request: "./src/index.js"} => ModuleGraphModule{ incomingConnections: Set(1) [ // entry 模塊,對應 originModule 爲null ModuleGraphConnection{ module: NormalModule{request: "./src/index.js"}, originModule:null } ], outgoingConnections: Set(2) [ // 從 index 指向 a 模塊 ModuleGraphConnection{ module: NormalModule{request: "./src/a.js"}, originModule: NormalModule{request: "./src/index.js"} }, // 從 index 指向 b 模塊 ModuleGraphConnection{ module: NormalModule{request: "./src/b.js"}, originModule: NormalModule{request: "./src/index.js"} } ] }, NormalModule{request: "./src/a.js"} => ModuleGraphModule{ incomingConnections: Set(1) [ ModuleGraphConnection{ module: NormalModule{request: "./src/a.js"}, originModule: NormalModule{request: "./src/index.js"} } ], // a 模塊沒有其餘依賴,故 outgoingConnections 屬性值爲 undefined outgoingConnections: undefined }, NormalModule{request: "./src/b.js"} => ModuleGraphModule{ incomingConnections: Set(1) [ ModuleGraphConnection{ module: NormalModule{request: "./src/b.js"}, originModule: NormalModule{request: "./src/index.js"} } ], // b 模塊沒有其餘依賴,故 outgoingConnections 屬性值爲 undefined outgoingConnections: undefined } } }
從上面的 Dependency Graph 能夠看出,本質上 ModuleGraph._moduleMap
已經造成了一個有向無環圖結構,其中字典 _moduleMap
的 key 爲圖的節點,對應 value ModuleGraphModule
結構中的 outgoingConnections
屬性爲圖的邊,則上例中從起點 index.js
出發沿 outgoingConnections
向前可遍歷出圖的全部頂點。
以 webpack\@v5.16.0 爲例,關鍵字 moduleGraph
出現了 1277 次,幾乎覆蓋了 webpack/lib
文件夾下的全部文件,其做用可見一斑。雖然出現的頻率很高,但總的來講能夠看出有兩個主要做用:信息索引、轉變爲 ChunkGraph
以肯定輸出結構。
ModuleGraph
類型提供了不少實現 module / dependency 信息查詢的工具函數,例如:
getModule(dep: Dependency)
:根據 dep 查找對應的 module
實例getOutgoingConnections(module: Module)
:查找 module
實例的全部依賴getIssuer(module: Module)
:查找 module
在何處被引用(關於 issuer 機制的更多信息,可參考個人另外一篇文章: 十分鐘精進 Webpack:module.issuer 屬性詳解 )等等。
Webpack\@v5.x 內部的許多插件、Dependency 子類、Module 子類的實現都須要用到這些工具函數查找特定模塊、依賴的信息,例如:
SplitChunksPlugin
在優化 chunks 處理中,須要使用 moduleGraph.getExportsInfo
查詢各個模塊的 exportsInfo
(模塊導出的信息集合,與 tree-shaking 強相關,後續會單出一篇文章講解)信息以肯定如何分離 chunk
。compilation.seal
函數中,須要遍歷 entry 對應的 dep 並調用 moduleGraph.getModule
獲取完整的 module 定義那麼,在您編寫插件時,能夠考慮適度參考 webpack/lib/ModuleGraph.js
中提供的方法,確承認以獲取使用那些函數獲取到您所須要的信息。
Webpack 主體流程中,make 構建階段結束以後會進入 seal
階段,開始梳理以何種方式組織輸出內容。在 webpack\@v4.x 時,seal
階段主要圍繞 Chunk
及 ChunkGroup
兩個類型展開,而到了 5.0 以後,與 Dependency Graph 相似也引入了一套全新的基於 ChunkGraph
的圖結構實現資源生成算法。
在 compilation.seal 函數中,首先根據默認規則 —— 每一個 entry 對應組織爲一個 chunk ,以後調用 webpack/lib/buildChunkGraph.js
文件定義的 buildChunkGraph
方法,遍歷 make
階段生成的 moduleGraph
對象從而將 module 依賴關係轉化爲 chunkGraph
對象。
這一塊的邏輯也特別複雜,不在這裏展開,下次會單獨出一篇文章講解 chunk/chunkGroup/chunkGraph
等對象構築成的模塊輸出規則。
本文討論的 Dependency Graph 概念在 webpack 內部被大量使用,所以理解這個概念對咱們理解 webpack 源碼,或者學習如何編寫插件、loader 都會有極大的幫助。在分析過程其實也挖掘出了不少新的知識盲點:
若是你也對上述問題感興趣,歡迎點贊關注,後續會圍繞 webpack 輸出更多有用的文章。
往期文章: