原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!webpack
在 this._addModuleChain
的回調裏,獲得了生成的入口 module
。觸發 compilation.hooks
:succeedEntry
後,執行 return callback(null, module)
,回到文件 Compile.js
的 compile
的 make
鉤子的回調裏:web
this.hooks.make.callAsync(compilation, err => {
//...
compilation.finish(err => {
//...
compilation.seal(err => {
//...
this.hooks.afterCompile.callAsync(compilation, err => {
//...
return callback(null, compilation);
});
});
});
});
複製代碼
執行 compilation.finish
,觸發 compilation.hooks
:finishModules
,執行插件 FlagDependencyExportsPlugin
註冊的事件,做用是遍歷全部 module
將 export
出來的變量以數組的形式,單獨存儲到 module.buildMeta.providedExports
變量下。json
而後執行 reportDependencyErrorsAndWarnings
收集生成每個 module
時暴露出來的 err
和 warning
。數組
最後走回調執行 compilation.seal
。緩存
compilation.seal
裏觸發了海量 hooks
,爲咱們侵入 webpack
構建流程提供了海量鉤子。先執行(咱們先略過沒有註冊方法的鉤子):安全
this.hooks.seal.call();
複製代碼
觸發插件 WarnCaseSensitiveModulesPlugin
:模塊文件路徑須要區分大小寫的警告異步
this.hooks.optimizeDependencies.call(this.modules);
複製代碼
production
模式會觸發插件:ide
SideEffectsFlagPlugin
:識別 package.json
或者 module.rules
的 sideEffects
標誌(純的 ES2015 模塊),安全地刪除未用到的 export
導出FlagDependencyUsagePlugin
:編譯時標記依賴 unused harmony export
用於 Tree shaking
在觸發 compilation.hooks
:beforeChunks
後,開始遍歷入口對象 this._preparedEntrypoints
,爲每個入口生成一個 chunk
:優化
const chunk = this.addChunk(name);
複製代碼
該方法裏作了緩存判斷後執行 new Chunk(name)
,並同時添加 chunk
到 Compilation.chunks
,繼續執行:ui
const entrypoint = new Entrypoint(name);
複製代碼
Entrypoint
類擴展於 ChunkGroup
類,是 chunks
的集合,主要用來優化 chunk graph
。
繼續執行設置了 Compilation.runtimeChunk & Compilation.namedChunkGroups & Compilation.entrypoints & Compilation.chunkGroups
和 ChunkGroup.origins
,而後執行:
GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
GraphHelpers.connectChunkAndModule(chunk, module);
複製代碼
創建了 chunk
與 entrypoint
,chunk
與 module
之間的聯繫,而後執行:
this.assignDepth(module);
複製代碼
根據各個模塊依賴的深度(屢次依賴取最小值)設置 module.depth
,入口模塊則爲 depth = 0
。
遍歷完 this._preparedEntrypoints
後,而後執行:
buildChunkGraph(this, /** @type {Entrypoint[]} */ (this.chunkGroups.slice()));
複製代碼
buildChunkGraph
用於生成並優化 chunk
依賴圖,創建起各模塊以前的關係。分爲三階段:
// PART ONE
visitModules(compilation, inputChunkGroups, chunkGroupInfoMap, chunkDependencies, blocksWithNestedBlocks, allCreatedChunkGroups);
// PART TWO
connectChunkGroups(blocksWithNestedBlocks, chunkDependencies, chunkGroupInfoMap);
// Cleaup work
cleanupUnconnectedGroups(compilation, allCreatedChunkGroups);
複製代碼
先執行:
const blockInfoMap = extraceBlockInfoMap(compilation);
複製代碼
獲得一個 map
結構: module
與該 module
內導入其餘模塊的關係,同步存入 modules
,異步存入 blocks
。以 demo
爲例,獲得 blockInfoMap
:
{
//...map結構
0:{
key:NormalModule, //a
value:{
blocks:[ImportDependenciesBlock],//異步
modules:[NormalModule] //b modules爲set結構
}
},
1:{
key: ImportDependenciesBlock,
value:{
blocks: [],
modules:[NormalModule] //c
}
}
2:{
key: NormalModule, //c
value:{
blocks: [ImportDependenciesBlock],
modules:[NormalModule] //d
}
}
//........
}
複製代碼
繼續執行,設置了 queue
數組,push
入口 module
和對應的 action
等信息組成的對象,用於 while
循環;設置了 chunkGroupInfoMap
,他映射了 chunkGroup
和與他相關的信息對象,而後執行:
while (queue.length) {
//...
while (queue.length) {
//...
if (chunkGroup !== queueItem.chunkGroup) {
// 重置更新chunkGroup
}
switch (queueItem.action) {
case ADD_AND_ENTER_MODULE: {
// 創建chunk和module之間的聯繫
}
case ENTER_MODULE: {
// 設置 chunkGroup._moduleIndices 和 module.index,而後 queue.push 一個新的該 module 的 queueItem,action 設爲 LEAVE_MODULE
}
case PROCESS_BLOCK: {
// 1. 遍歷 blockInfoMap 裏的同步模塊 modules,若是對應 chunk 已有此模塊則跳過,若是 minAvailableModules 有此模塊則一個新的 queueItem 存入 skippedItems 數組,沒有則該 queueItem 存入 queue,其中 queue 的 action 都設爲 ADD_AND_ENTER_MODULE
// 2. 遍歷 blockInfoMap 裏的異步模塊 blocks
// 2.1 建立一個對應import依賴的chunkGroup和chunk,並創建二者的聯繫,而後更新了 compilation.chunkGroups 和 compilation.namedChunkGroups,chunkGroupCounters(計數 map),blockChunkGroups(映射依賴和 ChunkGroup的關係 map),allCreatedChunkGroups(收集被建立的ChunkGroup set)
// 2.2 更新 chunkDependencies(map) 創建前一個 ChunkGroup 與新的 ChunkGroup 和 import 依賴的映射
// 2.3 更新 queueConnect(map) 創建前一個 ChunkGroup 與新的 ChunkGroup 的映射
// 2.4 更新 queueDelayed,同 queue,注意 module 是前一個的 module
}
case LEAVE_MODULE: {
// 設置 chunkGroup._moduleIndices2 和 module.inde2
}
}
}
while (queueConnect.size > 0) {
// 1. 在 chunkGroupInfoMap 中設置前一個 ChunkGroup 的信息對象的 resultingAvailableModules, children
// 2. 在 chunkGroupInfoMap 中初始化新的 ChunkGroup 與他相關的信息對象的映射並設置了 availableModulesToBeMerged
if (outdatedChunkGroupInfo.size > 0) {
// 1.獲取設置新的 ChunkGroup 信息對象的 minAvailableModules
// 2.若是新的 ChunkGroup 信息對象的 skippedItems 不爲空則 push 到 queue
// 3.若是新的 ChunkGroup 信息對象的 children 不爲空,則更新 queueConnect 遞歸循環
}
}
// 把queueDelayed 放入queue走while的最外層循環,目的的同步循環處理完後,而後才處理異步module
if (queue.length === 0) {
const tempQueue = queue;
queue = queueDelayed.reverse();
queueDelayed = tempQueue;
}
}
複製代碼
while
對 queue.length
循環裏( while+push
防遞歸爆棧,後序深度優先),從入口 module
開始,解析了全部同步 module
並創建了 module
與 chunk
的聯繫;解析了全部第一層異步的 module
,併爲每一個不一樣 mudule
都新建了 chunkGroup
和 chunk
並創建了二者的聯繫。while
對 queueConnect.size
的循環裏,更新了 chunkGroupInfoMap
中前一個 ChunkGroup
的信息對象和初始化了新的 ChunkGroup
的信息對象,並獲取了最小可用模塊。module
,將 queueDelayed
賦給 queue
,走外部 while
對 queue.length
的循環。queue
裏的 block
爲 ImportDependenciesBlock
依賴,而後更新 chunkGroup
後, switch
走 PROCESS_BLOCK
得到本次異步對應的真正模塊,後面的處理數據都將在新的 ChunkGroup
信息對象上。就這樣循環處理,最終獲得一個 Map
結構的 chunkGroupInfoMap
。以本 demo
爲例,獲得:{
//...map結構
0:{
key:Entrypoint, //groupDebugId:5000
value:{
availableModulesToBeMerged:Array(0)
children:Set(1) {} //ChunkGroup 5001
chunkGroup:Entrypoint
minAvailableModules:Set(0)
minAvailableModulesOwned:true
resultingAvailableModules:Set(3)
skippedItems:Array(0)
}
},
1:{
key: ChunkGroup, //groupDebugId:5001
value:{
availableModulesToBeMerged:Array(0)
children:Set(1) {} //ChunkGroup 5002
chunkGroup:Entrypoint
minAvailableModules:Set(3)
minAvailableModulesOwned:true
resultingAvailableModules:Set(5)
skippedItems:Array(0)
}
}
2:{
key: ChunkGroup, //groupDebugId:5002
value:{
availableModulesToBeMerged:Array(0)
children:undefined
chunkGroup:Entrypoint
minAvailableModules:Set(5)
minAvailableModulesOwned:true
resultingAvailableModules:undefined
skippedItems:Array(1)
}
}
}
複製代碼
遍歷 chunkDependencies
,chunkDependencies
是 Map
結構,保存着前一個 ChunkGroup
與新的 ChunkGroup
和 import
依賴之間的映射:
{
//...map結構
0:{
key:Entrypoint, //groupDebugId:5000
value:[
{
block:ImportDependenciesBlock,
chunkGroup:ChunkGroup //groupDebugId:5001
}
]
},
1:{
key:ChunkGroup, //groupDebugId:5001
value:[
{
block:ImportDependenciesBlock,
chunkGroup:ChunkGroup //groupDebugId:5002
}
]
},
}
複製代碼
在判斷若是前一個 ChunkGroup
信息對象的可用模塊 resultingAvailableModules
包含後一個 ChunkGroup.chunks[]._modules
,則分別創建 import
依賴與對應的 ChunkGroup
,前一個 chunkGroup
和後一個 chunkGroup
的關係:
GraphHelpers.connectDependenciesBlockAndChunkGroup(depBlock, depChunkGroup); // ImportDependenciesBlock與chunkGroup創建聯繫
GraphHelpers.connectChunkGroupParentAndChild(chunkGroup, depChunkGroup); // chunkGroup之間創建聯繫:_children和_parents
複製代碼
遍歷 allCreatedChunkGroups
,allCreatedChunkGroups
即爲異步被建立的 ChunkGroup
,判斷 chunkGroup
有沒有父的 chunkGroup
(_parents
),若是沒有執行:
for (const chunk of chunkGroup.chunks) {
const idx = compilation.chunks.indexOf(chunk);
if (idx >= 0) compilation.chunks.splice(idx, 1);
chunk.remove('unconnected');
}
chunkGroup.remove('unconnected');
複製代碼
即解除 module,chunkGroup,chunk
三者之間的聯繫。
最終每一個 module
與每一個 chunk
,每一個 chunkGroup
和他們之間都創建了聯繫,優化造成了 chunk Graph
。
seal
裏繼續執行,先將 compilation.modules
按 index
屬性大小排序,而後執行:
this.hooks.afterChunks.call(this.chunks);
複製代碼
觸發插件 WebAssemblyModulesPlugin
:設置與 webassembly
相關的報錯信息,到此 chunk
生成結束。
finish
回調中執行的 seal
方法裏,包含了海量鉤子用於咱們侵入 webpack
的封包階段;chunk
時,同時實例化了 Entrypoint
等,並創建了入口 module
和 chunk
,Entrypoint
之間的聯繫;buildChunkGraph
的三個階段,讓全部的 module、chunk、chunkGroup
之間都創建了聯繫,造成了 chunk Graph
。afterChunks
標誌這 chunk
生成結束。