webpack 4 源码主流程分析(八):生成 chunk
module
构建完成后,回到文件 Compiler.js
的 compile
的 make
钩子的回调里:
1 | this.hooks.make.callAsync(compilation, (err) => { |
compilation.finish & compilation.seal
执行 compilation.finish
,触发 compilation.hooks
:finishModules
,执行插件 FlagDependencyExportsPlugin
注册的事件,作用是遍历所有 module
将 export
出来的变量以数组的形式,单独存储到 module.buildMeta.providedExports
变量下。
然后执行 reportDependencyErrorsAndWarnings
收集生成每一个 module
时暴露出来的 err
和 warning
。
然后走回调执行 compilation.seal
触发了海量 hooks
,为我们侵入 webpack
构建流程提供了海量钩子。我们略过本 demo
没有注册方法的钩子,执行:
1 | this.hooks.seal.call(); |
触发插件 WarnCaseSensitiveModulesPlugin
:模块文件路径需要区分大小写的警告
1 | this.hooks.optimizeDependencies.call(this.modules); |
production
模式会触发插件:
SideEffectsFlagPlugin
:识别package.json
或者module.rules
的sideEffects
标志(纯的 ES2015 模块),安全地删除未用到的export
导出FlagDependencyUsagePlugin
:编译时标记依赖unused harmony export
用于Tree shaking
chunk 初始化
在触发 compilation.hooks
:beforeChunks
后,开始遍历入口对象 this._preparedEntrypoints
,为每一个入口生成一个 chunk
:
1 | const chunk = this.addChunk(name); |
该方法里做了缓存判断后执行 new Chunk(name)
,并同时添加 chunk
到 Compilation.chunks
,继续执行:
1 | const entrypoint = new Entrypoint(name); |
Entrypoint
类扩展于 ChunkGroup
类,是 chunks
的集合,主要用来优化 chunk graph
。
继续执行设置了 Compilation.runtimeChunk & Compilation.namedChunkGroups & Compilation.entrypoints & Compilation.chunkGroups
和 ChunkGroup.origins
,然后执行:
1 | GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk); |
建立了 chunk
与 entrypoint
,chunk
与 module
之间的联系,然后执行:
1 | this.assignDepth(module); |
根据各个模块依赖的深度(多次依赖取最小值)设置 module.depth
,入口模块则为 depth = 0
。
遍历完 this._preparedEntrypoints
后,然后执行:
生成 chunk graph
1 | buildChunkGraph(this, /** @type {Entrypoint[]} */ (this.chunkGroups.slice())); |
buildChunkGraph
用于生成并优化 chunk
依赖图,建立起各模块之前的关系。 分为三阶段:
1 | // PART ONE |
第一阶段
第一阶段主要建立了 chunkGroup,chunk,module
(包括同步异步)之间的从属关系。
先执行:
1 | const blockInfoMap = extraceBlockInfoMap(compilation); |
得到一个 map
结构: module
与该 module
内导入其他模块的关系,同步存入 modules
,异步存入 blocks
。以 demo
为例,得到 blockInfoMap
:
1 | { |
继续执行,设置了 queue
数组,push
入口 module
和对应的 action
等信息组成的对象,用于 while
循环:
1 | queue.push({ |
设置了 chunkGroupInfoMap
,他映射了 chunkGroup
和与他相关的信息对象:
1 | chunkGroupInfoMap.set(chunkGroup, { |
然后执行:
1 | while (queue.length) { |
- 在内部
while
对queue.length
循环里(while+push
防递归爆栈,后序深度优先),从入口module
开始,解析了所有同步module
并建立了module
与chunk
的联系;解析了所有第一层异步(即非嵌套异步模块)的module
,并为每个不同的异步mudule
都新建了chunkGroup
和chunk
并建立了两者的联系。 - 然后在
while
对queueConnect.size
的循环里,更新了chunkGroupInfoMap
中前一个ChunkGroup
的信息对象和初始化了新的ChunkGroup
的信息对象,并获取了最小可用module
。 - 同步
module
循环处理结束后,开始处理异步module
,将queueDelayed
赋给queue
,走外部while
对queue.length
的循环。 - 处理异步模块的时候,
queue
里的block
为ImportDependenciesBlock
依赖,然后更新chunkGroup
后,switch
走PROCESS_BLOCK
获得本次异步对应的真正模块,后面的处理数据都将在新的ChunkGroup
信息对象上。就这样循环处理,最终得到一个Map
结构的chunkGroupInfoMap
。以本demo
为例,得到:
1 | { |
第二阶段
第二阶段主要根据 ImportDependenciesBlock
建立了不同 chunkGroup
之间的父子关系。
遍历 chunkDependencies
,chunkDependencies
是 Map
结构,保存着前一个 ChunkGroup
与新的 ChunkGroup
和 import
依赖之间的映射:
1 | { |
在判断如果前一个 ChunkGroup
信息对象的可用模块 resultingAvailableModules
包含后一个 ChunkGroup.chunks[]._modules
,则分别建立 import
依赖与对应的 ChunkGroup
,前一个 chunkGroup
和后一个 chunkGroup
的关系:
1 | GraphHelpers.connectDependenciesBlockAndChunkGroup(depBlock, depChunkGroup); // ImportDependenciesBlock与chunkGroup建立联系 |
第三阶段
第三阶段主要清理了无用 chunk
并清理相关的联系。
遍历 allCreatedChunkGroups
,allCreatedChunkGroups
即为异步被创建的 ChunkGroup
,判断 chunkGroup
有没有父的 chunkGroup
(_parents
),如果没有执行:
1 | for (const chunk of chunkGroup.chunks) { |
即解除 module,chunkGroup,chunk
三者之间的联系。
最终每个 module
与每个 chunk
,每个 chunkGroup
和他们之间都建立了联系,优化形成了 chunk Graph
。
seal
里继续执行,先将 compilation.modules
按 index
属性大小排序,然后执行:
1 | this.hooks.afterChunks.call(this.chunks); |
触发插件 WebAssemblyModulesPlugin
:设置与 webassembly
相关的报错信息,到此 chunk
生成结束。
module chunk ChunkGroup 区别
module
module
即每一个资源文件的模块对应,如 js / css / 图片
等。由 NormalModule
实例化而来,存于 compilation.modules
。
module.blocks
: 异步模块的依赖module.dependencies
存同步模块的依赖module._chunks
保存module
所属chunk
列表
chunk
chunk
即每一个输出文件的对应,包括入口文件,异步加载文件,优化切割后的文件等等,存于 compilation.chunks
。
chunk._groups
: 保存chunk
所属ChunkGroup
列表chunk._modules
: 由哪些module
组成
ChunkGroup
ChunkGroup
一般包含一个 chunk
(入口 chunk
或异步模块的 chunk
)。entrypoint
就是一个 ChunkGroup
,里包含入口 chunk
。存于 compilation.chunkGroups
。
ChunkGroup.chunks
: 由哪些chunk
组成ChunkGroup._blocks
: 保存异步依赖ImportDependenciesBlock
ChunkGroup._children
: 保存子ChunkGroup
ChunkGroup._parent
: 保存父ChunkGroup
本章小结
- 在
finish
回调中执行的seal
方法里,包含了海量钩子用于我们侵入webpack
的封包阶段; - 在遍历入口文件实例化生成
chunk
时,同时实例化了Entrypoint
等,并建立了入口module
和chunk
,Entrypoint
之间的联系; - 通过
buildChunkGraph
的三个阶段,让所有的module、chunk、chunkGroup
之间都建立了联系,形成了chunk Graph
。 - 最后触发钩子
afterChunks
标志这chunk
生成结束。