做者:肖磊javascript
GitHub:CommanderXLcss
這篇文章主要是經過源碼去探索下 webpack 是如何經過在編譯環節建立的 module graph 來生成對應的 chunk graph。java
首先來了解一些概念及其相互之間的關係:webpack
咱們都知道 webpack 打包構建時會根據你的具體業務代碼和 webpack 相關配置來決定輸出的最終文件,具體的文件的名和文件數量也與此相關。而這些文件就被稱爲 chunk。例如在你的業務當中使用了異步分包的 API:git
import('./foo.js').then(bar => bar())
複製代碼
在最終輸出的文件當中,foo.js
會被單獨輸出一個 chunk 文件。github
又或者在你的 webpack 配置當中,進行了有關 optimization 優化 chunk 生成的配置:web
module.exports = {
optimization: {
runtimeChunk: {
name: 'runtime-chunk'
}
}
}
複製代碼
最終 webpack 會將 webpack runtime chunk 單獨抽離成一個 chunk 後再輸出成一個名爲runtime-chunk.js
的文件。數組
而這些生成的 chunk 文件當中便是由相關的 module 模塊所構成的。緩存
接下來咱們就看下 webpack 在工做流當中是如何生成 chunk 的,首先咱們先來看下示例:app
// a.js (webpack config 入口文件)
import add from './b.js'
add(1, 2)
import('./c').then(del => del(1, 2))
-----
// b.js
import mod from './d.js'
export default function add(n1, n2) {
return n1 + n2
}
mod(100, 11)
-----
// c.js
import mod from './d.js'
mod(100, 11)
import('./b.js').then(add => add(1, 2))
export default function del(n1, n2) {
return n1 - n2
}
-----
// d.js
export default function mod(n1, n2) {
return n1 % n2
}
複製代碼
webpack 相關的配置:
// webpack.config.js
module.exports = {
entry: {
app: 'a.js'
},
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].bundle.[chunkhash:8].js',
publicPath: '/'
},
optimization: {
runtimeChunk: {
name: 'bundle'
}
},
}
複製代碼
其中 a.js 爲 webpack config 當中配置的 entry 入口文件,a.js 依賴 b.js/c.js,而 b.js 依賴 d.js,c.js 依賴 d.js/b.js。最終經過 webpack 編譯後,將會生成3個 chunk 文件,其中:
接下來咱們就經過源碼來看下 webpack 內部是經過什麼樣的策略去完成 chunk 的生成的。
在 webpack 的工做流程當中,當全部的 module 都被編譯完成後,進入到 seal 階段會開始生成 chunk 的相關的工做:
// compilation.js
class Compilation {
...
seal () {
...
this.hooks.beforeChunks.call();
// 根據 addEntry 方法中收集到入口文件組成的 _preparedEntrypoints 數組
for (const preparedEntrypoint of this._preparedEntrypoints) {
const module = preparedEntrypoint.module;
const name = preparedEntrypoint.name;
const chunk = this.addChunk(name); // 入口 chunk 且爲 runtimeChunk
const entrypoint = new Entrypoint(name); // 每個 entryPoint 就是一個 chunkGroup
entrypoint.setRuntimeChunk(chunk); // 設置 runtime chunk
entrypoint.addOrigin(null, name, preparedEntrypoint.request);
this.namedChunkGroups.set(name, entrypoint); // 設置 chunkGroups 的內容
this.entrypoints.set(name, entrypoint);
this.chunkGroups.push(entrypoint);
// 創建起 chunkGroup 和 chunk 之間的關係
GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
// 創建起 chunk 和 module 之間的關係
GraphHelpers.connectChunkAndModule(chunk, module);
chunk.entryModule = module;
chunk.name = name;
this.assignDepth(module);
}
this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
// 對 module 進行排序
this.sortModules(this.modules);
// 建立完 chunk 以後的 hook
this.hooks.afterChunks.call(this.chunks);
//
this.hooks.optimize.call();
while (
this.hooks.optimizeModulesBasic.call(this.modules) ||
this.hooks.optimizeModules.call(this.modules) ||
this.hooks.optimizeModulesAdvanced.call(this.modules)
) {
/* empty */
}
// 優化 module 以後的 hook
this.hooks.afterOptimizeModules.call(this.modules);
while (
this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) ||
this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) ||
// 主要涉及到 webpack config 當中的有關 optimization 配置的相關內容
this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups)
) {
/* empty */
}
// 優化 chunk 以後的 hook
this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
...
}
...
}
複製代碼
在這個過程中首先遍歷 webpack config 當中配置的入口 module,每一個入口 module 都會經過addChunk
方法去建立一個 chunk,而這個新建的 chunk 爲一個空的 chunk,即不包含任何與之相關聯的 module。以後實例化一個 entryPoint,而這個 entryPoint 爲一個 chunkGroup,每一個 chunkGroup 能夠包含多的 chunk,同時內部會有個比較特殊的 runtimeChunk(當 webpack 最終編譯完成後包含的 webpack runtime 代碼最終會注入到 runtimeChunk 當中)。到此僅僅是分別建立了 chunk 以及 chunkGroup,接下來便調用GraphHelpers
模塊提供的connectChunkGroupAndChunk
及connectChunkAndModule
方法來創建起 chunkGroup 和 chunk 之間的聯繫,以及 chunk 和 入口 module 之間(這裏還未涉及到依賴 module)的聯繫:
// GraphHelpers.js
/** * @param {ChunkGroup} chunkGroup the ChunkGroup to connect * @param {Chunk} chunk chunk to tie to ChunkGroup * @returns {void} */
GraphHelpers.connectChunkGroupAndChunk = (chunkGroup, chunk) => {
if (chunkGroup.pushChunk(chunk)) {
chunk.addGroup(chunkGroup);
}
};
/** * @param {Chunk} chunk Chunk to connect to Module * @param {Module} module Module to connect to Chunk * @returns {void} */
GraphHelpers.connectChunkAndModule = (chunk, module) => {
if (module.addChunk(chunk)) {
chunk.addModule(module);
}
};
複製代碼
例如在示例當中,入口 module 只配置了一個,那麼在處理 entryPoints 階段時會生成一個 chunkGroup 以及一個 chunk,這個 chunk 目前僅僅只包含了入口 module。咱們都知道 webpack 輸出的 chunk 當中都會包含與之相關的 module,在編譯環節進行到上面這一步僅僅創建起了 chunk 和入口 module 之間的聯繫,那麼 chunk 是如何與其餘的 module 也創建起聯繫呢?接下來咱們就看下 webpack 在生成 chunk 的過程中是如何與其依賴的 module 進行關聯的。
與此相關的即是 compilation 實例提供的processDependenciesBlocksForChunkGroups
方法。這個方法內部細節較爲複雜,它包含了兩個核心的處理流程:
咱們先經過一個總體的流程圖來大體瞭解下這個方法內部的處理過程:
在第一個步驟中,首先對此次 compliation 收集到的 modules 進行一次遍歷,在遍歷 module 的過程當中,會對這個 module 的 dependencies 依賴進行處理,獲取這個 module 的依賴模塊,同時還會處理這個 module 的 blocks(即在你的代碼經過異步 API 加載的模塊),每一個異步 block 都會被加入到遍歷的過程中,被當作一個 module 來處理。所以在此次遍歷的過程結束後會創建起基本的 module graph,包含普通的 module 及異步 module(block),最終存儲到一個 map 表(blockInfoMap)當中:
const iteratorBlockPrepare = b => {
blockInfoBlocks.push(b);
// 將 block 加入到 blockQueue 當中,從而進入到下一次的遍歷過程中
blockQueue.push(b);
};
// 此次 compilation 包含的全部的 module
for (const modules of this.modules) {
blockQueue = [module];
currentModule = module;
while (blockQueue.length > 0) {
block = blockQueue.pop(); // 當前正在被遍歷的 module
blockInfoModules = new Set(); // module 依賴的同步的 module
blockInfoBlocks = []; // module 依賴的異步 module(block)
if (block.variables) {
iterationBlockVariable(block.variables, iteratorDependency);
}
// 在 blockInfoModules 數據集(set)當中添加 dependencies 中的普通 module
if (block.dependencies) {
iterationOfArrayCallback(block.dependencies, iteratorDependency);
}
// 在 blockInfoBlocks 和 blockQueue 數組當中添加異步 module(block),這樣這些被加入到 blockQueue當中的
// module 也會進入到遍歷的環節,去獲取異步 module(block)的依賴
if (block.blocks) {
iterationOfArrayCallback(block.blocks, iteratorBlockPrepare);
}
const blockInfo = {
modules: Array.from(blockInfoModules),
blocks: blockInfoBlocks
};
// blockInfoMap 上保存了每一個 module 依賴的同步 module 及 異步 blocks
blockInfoMap.set(block, blockInfo);
}
}
複製代碼
在咱們的實例當中生成的 module graph 即爲:
當基礎的 module graph (即blockInfoMap
)生成後,接下來開始根據 module graph 去生成 basic chunk graph。剛開始仍然是數據的處理,將傳入的 entryPoint(chunkGroup) 轉化爲一個新的 queue,queue 數組當中每一項包含了:
在咱們提供的示例當中,由於是單入口的,所以這裏 queue 初始化後只有一項。
{
action: ENTER_MODULE,
block: a.js,
module: a.js,
chunk,
chunkGroup: entryPoint
}
複製代碼
接下來進入到 queue 的遍歷環節
// 建立異步的 block
// For each async Block in graph
/** * @param {AsyncDependenciesBlock} b iterating over each Async DepBlock * @returns {void} */
const iteratorBlock = b => {
// 1. We create a chunk for this Block
// but only once (blockChunkGroups map)
let c = blockChunkGroups.get(b);
if (c === undefined) {
c = this.namedChunkGroups.get(b.chunkName);
if (c && c.isInitial()) {
this.errors.push(
new AsyncDependencyToInitialChunkError(b.chunkName, module, b.loc)
);
c = chunkGroup;
} else {
// 經過 addChunkInGroup 方法建立新的 chunkGroup 及 chunk,並返回這個 chunkGroup
c = this.addChunkInGroup(
b.groupOptions || b.chunkName,
module, // 這個 block 所屬的 module
b.loc,
b.request
);
chunkGroupCounters.set(c, { index: 0, index2: 0 });
blockChunkGroups.set(b, c);
allCreatedChunkGroups.add(c);
}
} else {
// TODO webpack 5 remove addOptions check
if (c.addOptions) c.addOptions(b.groupOptions);
c.addOrigin(module, b.loc, b.request);
}
// 2. We store the Block+Chunk mapping as dependency for the chunk
let deps = chunkDependencies.get(chunkGroup);
if (!deps) chunkDependencies.set(chunkGroup, (deps = []));
// 當前 chunkGroup 所依賴的 block 及 chunkGroup
deps.push({
block: b,
chunkGroup: c,
couldBeFiltered: true
});
// 異步的 block 使用建立的新的 chunkGroup
// 3. We enqueue the DependenciesBlock for traversal
queueDelayed.push({
action: PROCESS_BLOCK,
block: b,
module: module,
chunk: c.chunks[0], // 獲取新建立的 chunkGroup 當中的第一個 chunk,即 block 須要被加入的 chunk
chunkGroup: c // 異步 block 使用新建立的 chunkGroup
});
};
...
const ADD_AND_ENTER_MODULE = 0;
const ENTER_MODULE = 1;
const PROCESS_BLOCK = 2;
const LEAVE_MODULE = 3;
...
const chunkGroupToQueueItem = chunkGroup => ({
action: ENTER_MODULE,
block: chunkGroup.chunks[0].entryModule,
module: chunkGroup.chunks[0].entryModule,
chunk: chunkGroup.chunks[0],
chunkGroup
});
let queue = inputChunkGroups.map(chunkGroupToQueueItem).reverse()
while (queue.length) { // 外層 queue 遍歷
while (queue.length) { // 內層 queue 遍歷
const queueItem = queue.pop();
module = queueItem.module;
block = queueItem.block;
chunk = queueItem.chunk;
chunkGroup = queueItem.chunkGroup;
switch (queueItem.action) {
case ADD_AND_ENTER_MODULE: {
// 添加 module 至 chunk 當中
// We connect Module and Chunk when not already done
if (chunk.addModule(module)) {
module.addChunk(chunk);
} else {
// already connected, skip it
break;
}
}
// fallthrough
case ENTER_MODULE: {
...
queue.push({
action: LEAVE_MODULE,
block,
module,
chunk,
chunkGroup
});
}
// fallthrough
case PROCESS_BLOCK: {
// get prepared block info
const blockInfo = blockInfoMap.get(block);
// Traverse all referenced modules
for (let i = blockInfo.modules.length - 1; i >= 0; i--) {
const refModule = blockInfo.modules[i];
if (chunk.containsModule(refModule)) {
// skip early if already connected
continue;
}
// enqueue the add and enter to enter in the correct order
// this is relevant with circular dependencies
queue.push({
action: ADD_AND_ENTER_MODULE,
block: refModule, // 依賴 module
module: refModule, // 依賴 module
chunk, // module 所屬的 chunk
chunkGroup // module 所屬的 chunkGroup
});
}
// 開始建立異步的 chunk
// Traverse all Blocks
iterationOfArrayCallback(blockInfo.blocks, iteratorBlock);
if (blockInfo.blocks.length > 0 && module !== block) {
blocksWithNestedBlocks.add(block);
}
break;
}
case LEAVE_MODULE: {
...
break;
}
}
}
const tempQueue = queue;
queue = queueDelayed.reverse();
queueDelayed = tempQueue;
}
複製代碼
經過源碼咱們發現對於 queue 的處理進行了2次遍歷的操做(內層和外層),具體爲何會須要進行2次遍歷操做後文會說明。首先咱們來看下內層的遍歷操做,首先根據 action 的類型進入到對應的處理流程當中:
首先進入到 ENTRY_MODULE 的階段,會在 queue 中新增一個 action 爲 LEAVE_MODULE 的項會在後面遍歷的流程當中使用,當 ENTRY_MODULE 的階段進行完後,當即進入到了 PROCESS_BLOCK 階段:
在這個階段當中根據 module graph 依賴圖保存的模塊映射 blockInfoMap 獲取這個 module(稱爲A) 的同步依賴 modules 及異步依賴 blocks。
接下來遍歷 modules 當中的包含的 module(稱爲B),判斷當前這個 module(A) 所屬的 chunk 當中是否包含了其依賴 modules 當中的 module(B),若是不包含的話,那麼會在 queue 當中加入新的項,新加入的項目的 action 爲 ADD_AND_ENTER_MODULE,即這個新增項在下次遍歷的時候,首先會進入到 ADD_AND_ENTER_MODULE 階段。
當新項被 push 至 queue 當中後,即這個 module 依賴的還未被處理的 module(A) 被加入到 queue 當中後,接下來開始調用iteratorBlock
方法來處理這個 module(A) 依賴的全部的異步 blocks,在這個方法內部主要完成的工做是:
調用addChunkInGroup
爲這個異步的 block 新建一個 chunk 以及 chunkGroup,同時調用 GraphHelpers 模塊提供的 connectChunkGroupAndChunk 創建起這個新建的 chunk 和 chunkGroup 之間的聯繫。這裏新建的 chunk 也就是在你的代碼當中使用異步API 加載模塊時,webpack 最終會單獨給這個模塊輸出一個 chunk,可是此時這個 chunk 爲一個空的 chunk,沒有加入任何依賴的 module;
創建起當前 module 所屬的 chunkGroup 和 block 以及這個 block 所屬的 chunkGroup 之間的依賴關係,並存儲至 chunkDependencies Map 表中,這個 Map 表主要用於後面優化 chunk graph;
向 queueDelayed 中添加一個 action 類型爲 PROCESS_BLOCK,module 爲當前所屬的 module,block 爲當前 module 依賴的異步模塊,chunk(chunkGroup 當中的第一個 chunk) 及 chunkGroup 都是處理異步模塊生成的新項,而這裏向 queueDelayed 數據集當中添加的新項主要就是用於 queue 的外層遍歷。
在 ENTRY_MODULE 階段即完成了將 entry module 的依賴 module 加入到 queue 當中,這個階段結束後即進入到了 queue 內層第二輪的遍歷的環節:
在對 queue 的內層遍歷過程中,咱們主要關注 queue 當中每項 action 類型爲 ADD_AND_ENTER_MODULE 的項,在進行實際的處理時,進入到 ADD_AND_ENTER_MODULE 階段,這個階段完成的主要工做就是判斷 chunk 所依賴的 module 是否已經添加到 chunk 內部(chunk.addModule
方法),若是沒有的話,那麼便會將 module 加入到 chunk,並進入到 ENTRY_MODULE 階段,進入到後面的流程(見上文),若是已經添加過了,那麼則會跳過此次遍歷。
當對 queue 這一輪的內層的遍歷完成後(每一輪的內層遍歷都對應於同一個 chunkGroup,即每一輪內層的遍歷都是對這個 chunkGroup 當中所包含的全部的 module 進行處理),開始進入到外層的遍歷當中,即對 queueDelayed 數據集進行處理。
以上是在processDependenciesBlocksForChunkGroups
方法內部對於 module graph 和 chunk graph 的初步處理,最終的結果就是根據 module graph 創建起了 chunk graph,將本來空的 chunk 裏面加入其對應的 module 依賴。
entryPoint 包含了 a, b, d 3個 module,而 a 的異步依賴模塊 c 以及 c 的同步依賴模塊 d 同屬於新建立的 chunkGroup2,chunkGroup2 中只有一個 chunk,而 c 的異步模塊 b 屬於新建立的 chunkGroup3。
接下來進入到第二個步驟,遍歷 chunk graph,經過和依賴的 module 之間的使用關係來創建起不一樣 chunkGroup 之間的父子關係,同時剔除一些沒有創建起聯繫的 chunk。
/** * Helper function to check if all modules of a chunk are available * * @param {ChunkGroup} chunkGroup the chunkGroup to scan * @param {Set<Module>} availableModules the comparitor set * @returns {boolean} return true if all modules of a chunk are available */
// 判斷chunkGroup當中是否已經包含了全部的 availableModules
const areModulesAvailable = (chunkGroup, availableModules) => {
for (const chunk of chunkGroup.chunks) {
for (const module of chunk.modulesIterable) {
// 若是在 availableModules 存在沒有的 module,那麼返回 false
if (!availableModules.has(module)) return false;
}
}
return true;
};
// For each edge in the basic chunk graph
/** * @param {TODO} dep the dependency used for filtering * @returns {boolean} used to filter "edges" (aka Dependencies) that were pointing * to modules that are already available. Also filters circular dependencies in the chunks graph */
const filterFn = dep => {
const depChunkGroup = dep.chunkGroup;
if (!dep.couldBeFiltered) return true;
if (blocksWithNestedBlocks.has(dep.block)) return true;
if (areModulesAvailable(depChunkGroup, newAvailableModules)) {
return false; // break, all modules are already available
}
dep.couldBeFiltered = false;
return true;
};
/** @type {Map<ChunkGroup, ChunkGroupInfo>} */
const chunkGroupInfoMap = new Map();
/** @type {Queue<ChunkGroup>} */
const queue2 = new Queue(inputChunkGroups);
for (const chunkGroup of inputChunkGroups) {
chunkGroupInfoMap.set(chunkGroup, {
minAvailableModules: undefined,
availableModulesToBeMerged: [new Set()]
});
}
...
while (queue2.length) {
chunkGroup = queue2.dequeue();
const info = chunkGroupInfoMap.get(chunkGroup);
const availableModulesToBeMerged = info.availableModulesToBeMerged;
let minAvailableModules = info.minAvailableModules;
...
}
...
複製代碼
首先仍是完成一些數據的初始化工做,chunkGroupInfoMap 存放了不一樣 chunkGroup 相關信息:
/** @type {Map<ChunkGroup, ChunkGroupInfo>} */
const chunkGroupInfoMap = new Map();
/** @type {Queue<ChunkGroup>} */
const queue2 = new Queue(inputChunkGroups);
for (const chunkGroup of inputChunkGroups) {
chunkGroupInfoMap.set(chunkGroup, {
minAvailableModules: undefined,
availableModulesToBeMerged: [new Set()]
});
}
複製代碼
完成以後,遍歷 queue2,其中的每一項都是 chunkGroup,初始爲 entry 對應的 chunkGroup,在咱們的示例中因爲存在動態加載的模塊c,因此也會加入到queue2隊列中當作一個「獨立」的 entry 處理,可是是存在父子關係的,它依託於入口 entry 所對應的 chunkGroup。
while (queue2.length) {
chunkGroup = queue2.dequeue();
const info = chunkGroupInfoMap.get(chunkGroup);
const availableModulesToBeMerged = info.availableModulesToBeMerged;
let minAvailableModules = info.minAvailableModules;
// 1. Get minimal available modules
// It doesn't make sense to traverse a chunk again with more available modules.
// This step calculates the minimal available modules and skips traversal when
// the list didn't shrink.
availableModulesToBeMerged.sort(bySetSize);
let changed = false;
for (const availableModules of availableModulesToBeMerged) {
if (minAvailableModules === undefined) {
minAvailableModules = new Set(availableModules);
info.minAvailableModules = minAvailableModules;
changed = true;
} else {
for (const m of minAvailableModules) {
if (!availableModules.has(m)) {
minAvailableModules.delete(m);
changed = true;
}
}
}
}
availableModulesToBeMerged.length = 0;
if (!changed) continue;
// 獲取這個 chunkGroup 的 deps 數組,包含異步的 block 及 對應的 chunkGroup
// 2. Get the edges at this point of the graph
const deps = chunkDependencies.get(chunkGroup);
if (!deps) continue;
if (deps.length === 0) continue;
// 根據以前的 minAvailableModules 建立一個新的 newAvailableModules 數據集
// 即以前全部遍歷過的 chunk 當中的 module 都會保存到這個數據集當中,不停的累加
// 3. Create a new Set of available modules at this points
newAvailableModules = new Set(minAvailableModules);
for (const chunk of chunkGroup.chunks) {
for (const m of chunk.modulesIterable) { // 這個 chunk 當中所包含的 module
newAvailableModules.add(m);
}
}
// 邊界條件,及異步的 block 所在的 chunkGroup
// 4. Foreach remaining edge
const nextChunkGroups = new Set();
// 異步 block 依賴
for (let i = 0; i < deps.length; i++) {
const dep = deps[i];
// Filter inline, rather than creating a new array from `.filter()`
if (!filterFn(dep)) {
continue;
}
// 這個 block 所屬的 chunkGroup,在 iteratorBlock 方法內部建立的
const depChunkGroup = dep.chunkGroup;
const depBlock = dep.block;
// 開始創建 block 和 chunkGroup 之間的關係
// 在爲 block 建立新的 chunk 時,僅僅創建起了 chunkGroup 和 chunk 之間的關係,
// 5. Connect block with chunk
GraphHelpers.connectDependenciesBlockAndChunkGroup(
depBlock,
depChunkGroup
);
// 創建起新建立的 chunkGroup 和此前的 chunkGroup 之間的相互聯繫
// 6. Connect chunk with parent
GraphHelpers.connectChunkGroupParentAndChild(chunkGroup, depChunkGroup);
nextChunkGroups.add(depChunkGroup);
}
// 7. Enqueue further traversal
for (const nextChunkGroup of nextChunkGroups) {
...
// As queue deduplicates enqueued items this makes sure that a ChunkGroup
// is not enqueued twice
queue2.enqueue(nextChunkGroup);
}
}
複製代碼
獲取在第一階段的 chunkDependencies 當中緩存的 chunkGroup 的 deps 數組依賴,chunkDependencies 中保存了不一樣 chunkGroup 所依賴的異步 block,以及同這個 block 一同建立的 chunkGroup(目前兩者僅僅是存於一個 map 結構當中,還未創建起 chunkGroup 和 block 之間的依賴關係)。
若是 deps 數據不存在或者長度爲0,那麼會跳過遍歷 deps 當中的 chunkGroup 流程,不然會爲這個 chunkGroup 建立一個新的 available module 數據集 newAvailableModules,開始遍歷這個 chunkGroup 當中全部的 chunk 所包含的 module,並加入到 newAvailableModules 這一數據集當中。並開始遍歷這個 chunkGroup 的 deps 數組依賴,這個階段主要完成的工做就是:
connectDependenciesBlockAndChunkGroup
創建起 deps 依賴中的異步 block 和 chunkGroup 的依賴關係;connectChunkGroupParentAndChild
創建起 chunkGroup 和 deps 依賴中的 chunkGroup 之間的依賴關係 (這個依賴關係也決定了在 webpack 編譯完成後輸出的文件當中是否會有 deps 依賴中的 chunkGroup 所包含的 chunk);那麼在咱們給出的示例當中,通過在上面提到的這些步驟,第一階段處理 entryPoint(chunkGroup),以及其包含的全部的 module,在處理過程當中發現這個 entryPoint 依賴異步 block c,它包含在了 blocksWithNestedBlocks 數據集當中,依據對應的過濾規則,是須要繼續遍歷異步 block c 所在的 chunkGroup2。接下來在處理 chunkGroup2 的過程中,它依賴 chunkGroup3,且這個 chunkGroup3 包含異步 block d,由於在第一階段處理 entryPoint 過程當中完成了一輪 module 集的收集,其中就包含了同步的 module d,這裏能夠想象獲得的是同步的 module d 和異步 block d 最終只可能輸出一個,且同步的 module d 要比異步的 block d 的優先級更高。所以最終模塊 d 的代碼會以同步的 module d 的形式被輸出到 entryPoint 所包含的 chunk 當中,這樣包含異步 block d 的 chunkGroup3 也就相應的不會再被輸出,即會被從 chunk graph 當中剔除掉。
最終會生成的 chunk 依賴圖爲:
以上就是經過源碼分析了 webpack 是如何構建 module graph,以及是如何經過 module graph 去生成 chunk graph 的,當你讀完這篇文章後應該就大體瞭解了在你每次構建完成後,你的項目應用中目標輸出文件夾出現的不一樣的 chunk 文件是通過哪些過程而產生的。