源文件以及配置文件
有以下文件 a.js / b.js / c.js / d.js 以及 webpack.config.js, 其中 a.js 为入口文件,它们之间的依赖关系如下图,实心箭头代表异步加载。
// 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。
chunk:一个 chunk 可以包含多个 module,可以通过 _groups 字段看出它所属的 chunkGroup,通过 _modules 看出它由哪些 module 组成。Webpack 在为每个入口创建 entrypoint 的同时,也会创建一个 chunk,如下图:
module:资源文件,如 js / css / 图片 等,可以通过 _chunks 看出它所属的 chunk,通过 blocks 看出它异步加载的模块。Webpack 会将它封装成 NormalModule 对象,’./src/a.js’ 对应的 NormalModule 对象如下图:
block:在模块中异步加载的模块,比如:import(’./c.js’).then(),Webpack 会将它封装成 ImportDependenciesBlock。在 a.js 中异步加载的 c.js 封装之后的结构如下:
简写说明:
为了在后续的源码解析中更清晰的描述,使用如下简写。另外:本次源码解析着重于 module graph & basic chunk graph 的创建,多余的分支不讲,因为我也没看。
- NM(’./src/a.js’):’./src/a.js’ 模块文件封装之后的 NormalModule.
- chunk(‘app’):Webpack 为入口创建的 chunk.
- chunkGroup(‘app’):Webpack 为入口创建的 chunkGroup.
- block(’./c.js’):异步加载的模块 ‘./c.js’ 封装之后的 ImportDependenciesBlock.Webpack 会为每个异步加载的模块创建一个 chunk & chunkGroup,并关联它们。
- chunk(’./c.js’):为异步加载的模块 ‘./c.js’ 创建的 chunk.
- chunkGroup(’./c.js’):为异步加载的模块 ‘./c.js’ 创建的 chunkGroup.
创建并关联入口 chunk & 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’))
processDependenciesBlocksForChunkGroups
接下来我们重点看一下
this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
该函数主要实现了两块功能:
1.创建 module graph,保存在 blockInfoMap.
2.根据 module graph 创建 basic chunk graph,即每个 chunk 收集好相应的 modules,保存在 chunkGroups.
创建 module graph
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 中。
创建 chunk graph
// 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 如下对象:
- 第一次内层循环 - queue pop $1,进入 case PROCESS_BLOCK: ,遍历 Block(’./b.js’) 依赖的 modules - [NM(’./b.js’)],如果 chunk(’./b.js’) 还未关联,就 push queue 如下对象:
$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.
如何 debug Webpack 源码
- 在 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
参考
webpack系列之六chunk图生成