有如下文件 a.js / b.js / c.js / d.js 以及 webpack.config.js, 其中 a.js 爲入口文件,它們之間的依賴關係以下圖,實心箭頭表明異步加載。javascript
// a.js - 入口文件
import add from './b.js'
add(1, 2)
import('./c.js').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.js
module.exports = {
entry: {
app: './src/a.js'
},
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].bundle.[chunkhash:8].js',
publicPath: '/'
},
optimization: {
runtimeChunk: {
name: 'bundle'
}
},
}
複製代碼
chunkGroup:一個 chunkGroup 能夠包含多個 chunk,能夠經過 chunks
字段看出它由哪些 chunk 組成。Webpack 會爲每一個入口建立一個 entrypoint,下圖就是入口 app: './src/a.js' 的 entrypoint,能夠看出 entrypoint 就是一個 chunkGroup。 css
chunk:一個 chunk 能夠包含多個 module,能夠經過 _groups
字段看出它所屬的 chunkGroup,經過 _modules
看出它由哪些 module 組成。Webpack 在爲每一個入口建立 entrypoint 的同時,也會建立一個 chunk,以下圖: java
module:資源文件,如 js / css / 圖片 等,能夠經過 _chunks
看出它所屬的 chunk,經過 blocks
看出它異步加載的模塊。Webpack 會將它封裝成 NormalModule 對象,'./src/a.js' 對應的 NormalModule 對象以下圖: node
block:在模塊中異步加載的模塊,好比:import('./c.js').then()
,Webpack 會將它封裝成 ImportDependenciesBlock。在 a.js 中異步加載的 c.js 封裝以後的結構以下: webpack
爲了在後續的源碼解析中更清晰的描述,使用以下簡寫。另外:本次源碼解析着重於 module graph & basic chunk graph 的建立,多餘的分支不講,由於我也沒看。web
NM('./src/a.js'):'./src/a.js' 模塊文件封裝以後的 NormalModule.chrome
chunk('app'):Webpack 爲入口建立的 chunk.npm
chunkGroup('app'):Webpack 爲入口建立的 chunkGroup.json
block('./c.js'):異步加載的模塊 './c.js' 封裝以後的 ImportDependenciesBlock.數組
Webpack 會爲每一個異步加載的模塊建立一個 chunk & chunkGroup,並關聯它們。
chunk('./c.js'):爲異步加載的模塊 './c.js' 建立的 chunk.
chunkGroup('./c.js'):爲異步加載的模塊 './c.js' 建立的 chunkGroup.
// compilation.js
class Compilation {
...
seal () {
...
// 建立 chunk 以前的 hook
this.hooks.beforeChunks.call();
// 根據 addEntry 方法中收集到入口文件組成的 _preparedEntrypoints 數組
for (const preparedEntrypoint of this._preparedEntrypoints) {
const module = preparedEntrypoint.module; // 即 NM('./src/a.js')
const name = preparedEntrypoint.name; // 即 'app'
const chunk = this.addChunk(name); // 爲每一個入口建立 chunk
const entrypoint = new Entrypoint(name); // 爲每一個入口建立 entrypoint,它就是 chunkGroup
entrypoint.setRuntimeChunk(chunk); // 設置爲 runtime chunk
entrypoint.addOrigin(null, name, preparedEntrypoint.request);
this.namedChunkGroups.set(name, entrypoint);
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.js optimization 配置
this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups)
) {
/* empty */
}
// 優化 chunk 以後的 hook
this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
...
}
...
}
// 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);
}
};
複製代碼
Webpack 會遍歷配置文件中的入口,爲每一個入口建立一個 chunk & chunkGroup,此時的 chunk 還沒收集任何 NormalModule,包括入口文件對應的 NormalModule。
將 chunk 設爲 runtimeChunk,當 Webpack 編譯完成後,webpack runtime 代碼會注入到 runtimeChunk。
調用 GraphHelpers.connectChunkGroupAndChunk()
和 GraphHelpers.connectChunkAndModule()
創建 chunk & chunkGroup 之間的關係,以及 chunk & 入口模塊 之間的關係。這裏還未涉及 chunk 和 入口模塊依賴的模塊的關係。
至此,Webpack 已經建立了 chunkGroup('app') & chunk('app'),chunk('app').modules = Set(NM('./src/a.js'))
接下來咱們重點看一下 this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
該函數主要實現了兩塊功能:
const iteratorDependency = d => {
// We skip Dependencies without Reference
const ref = this.getDependencyReference(currentModule, d);
if (!ref) {
return;
}
// We skip Dependencies without Module pointer
const refModule = ref.module;
if (!refModule) {
return;
}
// We skip weak Dependencies
if (ref.weak) {
return;
}
blockInfoModules.add(refModule);
};
const iteratorBlockPrepare = b => {
blockInfoBlocks.push(b);
// blockQueue push b(異步依賴),從而進入到下一次的內層循環
blockQueue.push(b);
};
// 本次 compilation 包含的全部 module
for (const modules of this.modules) {
blockQueue = [module];
currentModule = module;
while (blockQueue.length > 0) {
block = blockQueue.pop(); // 同步 module / 異步 block
blockInfoModules = new Set(); // 保存模塊依賴的同步 module
blockInfoBlocks = []; // 保存模塊依賴的異步 block
if (block.variables) {
iterationBlockVariable(block.variables, iteratorDependency);
}
// 在 blockInfoModules 中添加 dependencies 中的普通 module
if (block.dependencies) {
iterationOfArrayCallback(block.dependencies, iteratorDependency);
}
// 在 blockInfoBlocks 和 blockQueue 數組中添加異步 module,
// 這樣異步 block 也會進入到內層循環,去獲取異步 block 的依賴
if (block.blocks) {
iterationOfArrayCallback(block.blocks, iteratorBlockPrepare);
}
const blockInfo = {
modules: Array.from(blockInfoModules),
blocks: blockInfoBlocks
};
// blockInfoMap 上保存了每一個 module 依賴的同步 modules 及 異步 blocks
blockInfoMap.set(block, blockInfo);
}
}
複製代碼
this.modules = [NM('./src/a.js'), NM('./b.js'), NM('./c.js'), NM('./d.js')]
第一次外層循環:blockQueue = [NM('./src/a.js')], currentModule = NM('./src/a.js')
第一次內層循環 - blockQueue pop NM('./src/a.js'),開始處理 NM('./src/a.js')。先忽略 if (block.variables)
,進入 if (block.dependencies)
,遍歷 NM('./src/a.js').dependencies 執行 iteratorDependency,在 blockInfoModules 中添加 NM('./src/a.js') 的同步依賴;而後進入 if (block.blocks)
,遍歷 NM('./src/a.js').blocks 執行 iteratorBlockPrepare,在 blockInfoBlocks 中添加 NM('./src/a.js') 的異步依賴,而且 blockQueue push 該異步依賴。此時 blockQueue & blockInfoMap 以下:
blockQueue = [Block('./c.js')];
blockInfoMap = Map({
key: NM('./src/a.js'),
value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
})
複製代碼
第二次內層循環 - blockQueue pop Block('./c.js'),開始處理 Block('./c.js'),步驟同上,此時 blockQueue & blockInfoMap 以下,內層循環結束。
blockQueue = [];
blockInfoMap = Map({
key: NM('./src/a.js'),
value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
}, {
key: Block('./c.js'),
value: { modules: [NM('./c.js')], blocks: [] }
})
複製代碼
第二次外層循環:blockQueue = [NM('./b.js')], currentModule = NM('./b.js')
第一次內層循環 - blockQueue pop NM('./b.js'),開始處理 NM('./b.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:
blockQueue = [];
blockInfoMap = Map({
key: NM('./src/a.js'),
value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
}, {
key: Block('./c.js'),
value: { modules: [NM('./c.js')], blocks: [] }
}, {
key: NM('./b.js'),
value: { modules: [NM('./d.js')], blocks: [] }
})
複製代碼
第三次外層循環:blockQueue = [NM('./c.js')], currentModule = NM('./c.js')
第一次內層循環 - blockQueue pop NM('./c.js'),開始處理 NM('./c.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:
blockQueue = [Block('./b.js')];
blockInfoMap = Map({
key: NM('./src/a.js'),
value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
}, {
key: Block('./c.js'),
value: { modules: [NM('./c.js')], blocks: [] }
}, {
key: NM('./b.js'),
value: { modules: [NM('./d.js')], blocks: [] }
}, {
key: NM('./c.js'),
value: { modules: [NM('./d.js')], blocks: [Block('./b.js')] }
})
複製代碼
第二次內層循環 - blockQueue pop Block('./b.js'),開始處理 Block('./b.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:
blockQueue = [];
blockInfoMap = Map({
key: NM('./src/a.js'),
value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
}, {
key: Block('./c.js'),
value: { modules: [NM('./c.js')], blocks: [] }
}, {
key: NM('./b.js'),
value: { modules: [NM('./d.js')], blocks: [] }
}, {
key: NM('./c.js'),
value: { modules: [NM('./d.js')], blocks: [Block('./b.js')] }
}, {
key: Block('./b.js'),
value: { modules: [NM('./b.js')], blocks: [] }
})
複製代碼
第四次外層循環:blockQueue = [NM('./d.js')], currentModule = NM('./d.js')
第一次內層循環 - blockQueue pop NM('./d.js'),開始處理 NM('./d.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:
blockQueue = [];
blockInfoMap = Map({
key: NM('./src/a.js'),
value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
}, {
key: Block('./c.js'),
value: { modules: [NM('./c.js')], blocks: [] }
}, {
key: NM('./b.js'),
value: { modules: [NM('./d.js')], blocks: [] }
}, {
key: NM('./c.js'),
value: { modules: [NM('./d.js')], blocks: [Block('./b.js')] }
}, {
key: Block('./b.js'),
value: { modules: [NM('./b.js')], blocks: [] }
}, {
key: NM('./d.js'),
value: { modules: [NM('./d.js'), blocks: [] }
})
複製代碼
至此,咱們已經把本次 compilation 中全部模塊的同步 & 異步依賴信息保存在 blockInfoMap 中。
// 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) {
while (queue.length) {
const queueItem = queue.pop();
module = queueItem.module;
block = queueItem.block;
chunk = queueItem.chunk;
chunkGroup = queueItem.chunkGroup;
switch (queueItem.action) {
case ADD_AND_ENTER_MODULE: {
// 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
});
}
// 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 = [$1]
$1: {
action: ENTER_MODULE(1),
block: NM('./src/a.js'),
module: NM('./src/a.js'),
chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'))},
chunkGroup: Entrypoint{ chunks: [chunk('app')] }
}
複製代碼
第一次外層循環:
第一次內層循環 - queue pop $1,開始處理 chunk('app'),進入到 case ENTER_MODULE:
,忽略其餘代碼,直接看 queue.push({})
,push 以下對象:
$2: {
action: LEAVE_MODULE(3),
block: NM('./src/a.js'),
module: NM('./src/a.js'),
chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'))},
chunkGroup: Entrypoint{ chunks: [chunk('app')] }
}
複製代碼
注意 case ENTER_MODULE:
沒有 break,因此會接着進入 case PROCESS_BLOCK:
,上面獲得的 blockInfoMap
在這用到了,首先遍歷 NM('./src/a.js') 依賴的 modules - [NM('./b.js')],若是 chunk('app') 還未關聯,就 push queue 以下對象:
$3: {
action: ADD_AND_ENTER_MODULE(0),
block: NM('./b.js'),
module: NM('./b.js'),
chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'))},
chunkGroup: Entrypoint{ chunks: [chunk('app')] }
}
複製代碼
接着遍歷 NM('./src/a.js') 依賴的 blocks - [Block('./c.js')],針對每個 block,建立並關聯 chunkGroup & chunk,而後 queueDelayed.push 如下對象,queueDelayed 用於在第二次外層循環以前從新初始化 queue,後面會用到。至此第一次內層循環結束,此時 queue = [$2, $3], chunk('app').modules = Set(NM('./src/a.js'))
。
{
action: PROCESS_BLOCK(2),
block: Block('./c.js'),
module: NM('./src/a.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')]}
}
複製代碼
第二次內層循環 - queue pop $3,進入到 case ADD_AND_ENTER_MODULE:
,關聯 chunk('app') & NM('./b.js'),接着進入 case ENTER_MODULE:
,忽略其餘代碼,直接看 queue.push({})
,push 以下對象:
$4: {
action: LEAVE_MODULE(3),
block: NM('./b.js'),
module: NM('./b.js'),
chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'), NM('./b.js'))},
chunkGroup: Entrypoint{ chunks: [chunk('app')] }
}
複製代碼
接着進入 case PROCESS_BLOCK:
,遍歷 NM('./b.js') 依賴的 modules - [NM('./d.js')],若是 chunk('app') 還未關聯, 就 push queue 以下對象.
$5: {
action: ADD_AND_ENTER_MODULE(0),
block: NM('./d.js'),
module: NM('./d.js'),
chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'), NM('./b.js'))},
chunkGroup: Entrypoint{ chunks: [chunk('app')] }
}
複製代碼
因爲 NM('./b.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第二次內層循環結束,queue = [$2, $4, $5], chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'))
。
第三次內層循環 - queue pop $5,進入到 case ADD_AND_ENTER_MODULE:
,關聯 chunk('app') & NM('./d.js'),接着進入 case ENTER_MODULE:
,忽略其餘代碼,直接看 queue.push({})
,push 以下對象:
$6: {
action: LEAVE_MODULE(3),
block: NM('./d.js'),
module: NM('./d.js'),
chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))},
chunkGroup: Entrypoint{ chunks: [chunk('app')] }
}
複製代碼
遍歷 NM('./d.js') 依賴的 modules & blocks,因爲二者都爲空,因此沒有 queue.push,至此第三次內層循環結束,queue = [$2, $4, $6], chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))
。
接下來的三次內層循環都是進入 case LEAVE_MODULE:
,先無論裏面的邏輯。至此內層循環結束,此時 queue = [], chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))
。
使用上次外層循環中賦值的 queueDelayed 從新初始化 queue,queue = [$1],並將 queueDelayed 置空,開始關聯第二個 chunk & modules。
$1: {
action: PROCESS_BLOCK(2),
block: Block('./c.js'),
module: NM('./src/a.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')]}
}
複製代碼
第二次外層循環:
第一次內層循環 - queue pop $1,進入 case PROCESS_BLOCK:
,遍歷 Block('./c.js') 依賴的 modules - [NM('./d.js')],若是 chunk('./c.js') 還未關聯,就 push queue 以下對象:
$2: {
action: ADD_AND_ENTER_MODULE(0),
block: NM('./c.js'),
module: NM('./c.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')]}
}
複製代碼
因爲 Block('./c.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第一次內層循環結束,queue: [$2], chunk('./c.js').modules = Set(0)
。
第二次內層循環 - queue pop $2,進入 case ADD_AND_ENTER_MODULE:
,關聯 chunk('./c.js') & NM('./c.js'),接着進入 case ENTER_MODULE:
,忽略其餘代碼,直接看 queue.push({})
,push 以下對象:
$3: {
action: LEAVE_MODULE(3),
block: NM('./c.js'),
module: NM('./c.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./c.js'))},
chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')] }
}
複製代碼
接着進入 case PROCESS_BLOCK:
,遍歷 NM('./c.js') 依賴的 modules - [NM('./d.js')],若是 chunk('./c.js') 還未關聯,就 push queue 以下對象:
$4: {
action: ADD_AND_ENTER_MODULE(0),
block: NM('./d.js'),
module: NM('./d.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./c.js'))},
chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')] }
}
複製代碼
接着遍歷 NM('./c.js') 依賴的 blocks - [Block('./b.js')],針對每個 block,建立並關聯 chunkGroup & chunk; 而後 queueDelayed.push 如下對象,至此第二次內層循環結束,此時 queue = [$3, $4], chunk('./c.js').modules = Set(NM('./c.js'))
。
{
action: PROCESS_BLOCK(2),
block: Block('./b.js'),
module: NM('./c.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')]}
}
複製代碼
第三次內層循環 - queue pop $4,進入 case ADD_AND_ENTER_MODULE:
,關聯 chunk('./c.js') & NM('./d.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:
$6: {
action: LEAVE_MODULE(3),
block: NM('./d.js'),
module: NM('./d.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./c.js'), NM('./d.js'))},
chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')] }
}
複製代碼
進入 case PROCESS_BLOCK:
,NM('./d.js') 沒有依賴的 modules & blocks,至此第三次內層循環結束,此時 queue = [$3, $6], chunk('./c.js').modules = Set(NM('./c.js'), NM('./d.js'))
。 接下來的兩次內層循環都是進入 case LEAVE_MODULE:
,先無論裏面的邏輯。至此內層循環結束,此時 queue = [], chunk('./c.js').modules = Set(NM('./c.js'), NM('./d.js'))
。
使用上次外層循環中賦值的 queueDelayed 從新初始化 queue,queue = [$1],並將 queueDelayed 置空,開始關聯第三個 chunk & modules。
$1: {
action: PROCESS_BLOCK(2),
block: Block('./b.js'),
module: NM('./c.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')]}
}
複製代碼
第三次外層循環:
第一次內層循環 - queue pop $1,進入 case PROCESS_BLOCK:
,遍歷 Block('./b.js') 依賴的 modules - [NM('./b.js')],若是 chunk('./b.js') 還未關聯,就 push queue 以下對象:
$2: {
action: ADD_AND_ENTER_MODULE(0),
block: NM('./b.js'),
module: NM('./b.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')]}
}
複製代碼
因爲 Block('./b.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第一次內層循環結束,queue: [$2], chunk('./b.js').modules = Set(0)
。
第二次內層循環 - queue pop $2,進入 case ADD_AND_ENTER_MODULE:
,關聯 chunk('./b.js') & NM('./b.js'),接着進入 case ENTER_MODULE:
,忽略其餘代碼,直接看 queue.push({})
,push 以下對象:
$3: {
action: LEAVE_MODULE(3),
block: NM('./b.js'),
module: NM('./b.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./b.js'))},
chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')] }
}
複製代碼
接着進入 case PROCESS_BLOCK:
,遍歷 NM('./b.js') 依賴的 modules - [NM('./d.js')],若是 chunk('./b.js') 還未關聯,就 push queue 以下對象:
$4: {
action: ADD_AND_ENTER_MODULE(0),
block: NM('./d.js'),
module: NM('./d.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./b.js'))},
chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')] }
}
複製代碼
因爲 Block('./b.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第二次內層循環結束,queue: [$3, $4], chunk('./b.js').modules = Set(NM('./b.js'))。
第三次內層循環 - queue pop $4,進入 case ADD_AND_ENTER_MODULE:
,關聯 chunk('./b.js') & NM('./d.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:
$5: {
action: LEAVE_MODULE(3),
block: NM('./d.js'),
module: NM('./d.js'),
chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./b.js'), NM('./d.js'))},
chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')] }
}
複製代碼
接着進入 case PROCESS_BLOCK:
,NM('./d.js') 沒有依賴的 modules & blocks,至此第三次內層循環結束,此時 queue = [$3, $5], chunk('./b.js').modules = Set(NM('./b.js'), NM('./d.js'))
。 接下來的兩次內層循環都是進入 case LEAVE_MODULE:
,先無論裏面的邏輯。至此內層循環結束,此時 queue = [], chunk('./b.js').modules = Set(NM('./b.js'), NM('./d.js'))
。
至此循環結束,咱們獲得了三個 chunkGroup,每一個 chunkGroup 關聯的 chunk 分別是 chunk('app') / chunk('./c.js') / chunk('./b.js'),它們關聯的 modules 以下:
chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))
chunk('./c.js').modules = Set(NM('./c.js'), NM('./d.js'))
chunk('./b.js').modules = Set(NM('./b.js'), NM('./d.js'))
複製代碼
能夠看到 NM('./d.js') 在三個 chunk 中都存在,難道最終生成的每一個 bundle 中都會打包 NM('./d.js') 麼?固然不會,後續 webppack 還會優化 basic chunk graph,生成 chunk graph.
在 package.json 中添加一條 script
"debug": "node --inspect-brk ./node_modules/webpack/bin/webpack.js"
終端運行 npm run debug
,在瀏覽器地址欄輸入 chrome://inspect/#devices
,界面以下:
點擊 Open dedicated DevTools for Node,會打開 chrome 調試工具,開始調試 Webpack