# Webpack 实现
# 流程提炼
我们先提炼出 Webpack 核心工作过程中的关键环节,明确“查阅”源码的思路:
- Webpack CLI 启动打包流程;
- 载入 Webpack 核心模块,创建 Compiler 对象;
- 使用 Compiler 对象开始编译整个项目;
- 从入口文件开始,解析模块依赖,形成依赖关系树;
- 递归依赖树,将每个模块交给对应的 Loader 处理;
- 合并 Loader 处理完的结果,将打包结果输出到 dist 目录。
# Hook 的使用方式
Hook 的使用分为四步:
- 在构造函数中定义 Hook 类型和参数,生成 Hook 对象。
- 在插件中注册 Hook,添加对应 Hook 触发时的执行函数。
- 生成插件实例,运行 apply 方法。
- 在运行到对应生命周期节点时调用 Hook,执行注册过的插件的回调函数。如下面的代码所示:
lib/Compiler.js
this.hooks = {
...
make: new SyncHook(['compilation', 'params']), //1. 定义Hook
...
}
...
this.hooks.compilation.call(compilation, params); //4. 调用Hook
...
lib/dependencies/CommonJsPlugin.js
//2. 在插件中注册Hook
compiler.hooks.compilation.tap("CommonJSPlugin", (compilation, { contextModuleFactory, normalModuleFactory }) => {
...
})
lib/WebpackOptionsApply.js
//3. 生成插件实例,运行apply方法
new CommonJsPlugin(options.module).apply(compiler);
以上就是 Webpack 中 Hook 的一般使用方式。正是通过这种方式,Webpack 将编译器和编译过程的生命周期节点提供给外部插件,从而搭建起弹性化的工作引擎。
# Compiler Hooks
构建器实例的生命周期可以分为 3 个阶段:初始化阶段、构建过程阶段、产物生成阶段。下面我们就来大致介绍下这些不同阶段的 Hooks :
初始化阶段
- environment、afterEnvironment:在创建完 compiler 实例且执行了配置内定义的插件的 apply 方法后触发。
- entryOption、afterPlugins、afterResolvers:在 WebpackOptionsApply.js 中,这 3 个 Hooks 分别在执行 EntryOptions 插件和其他 Webpack 内置插件,以及解析了 resolver 配置后触发。
构建过程阶段
- normalModuleFactory、contextModuleFactory:在两类模块工厂创建后触发。
- beforeRun、run、watchRun、beforeCompile、compile、thisCompilation、compilation、make、afterCompile:在运行构建过程中触发。
产物生成阶段
- shouldEmit、emit、assetEmitted、afterEmit:在构建完成后,处理产物的过程中触发。
- failed、done:在达到最终结果状态时触发。
# Compilation Hooks
构建过程实例的生命周期我们分为两个阶段:
构建阶段
- addEntry、failedEntry、succeedEntry:在添加入口和添加入口结束时触发(Webpack 5 中移除)。
- buildModule、rebuildModule、finishRebuildingModule、failedModule、succeedModule:在构建单个模块时触发。
- finishModules:在所有模块构建完成后触发。
优化阶段
优化阶段在 seal 函数中共有 12 个主要的处理过程,如下图所示:
每个过程都暴露了相应的 Hooks,分别如下:
- seal、needAdditionalSeal、unseal、afterSeal:分别在 seal 函数的起始和结束的位置触发。
- optimizeDependencies、afterOptimizeDependencies:触发优化依赖的插件执行,例如 FlagDependencyUsagePlugin。
- beforeChunks、afterChunks:分别在生成 Chunks 的过程的前后触发。
- optimize:在生成 chunks 之后,开始执行优化处理的阶段触发。
- optimizeModule、afterOptimizeModule:在优化模块过程的前后触发。
- optimizeChunks、afterOptimizeChunks:在优化 Chunk 过程的前后触发,用于 Tree Shaking (opens new window)。
- optimizeTree、afterOptimizeTree:在优化模块和 Chunk 树过程的前后触发。
- optimizeChunkModules、afterOptimizeChunkModules:在优化 ChunkModules 的过程前后触发,例如 ModuleConcatenationPlugin,利用这一 Hook 来做Scope Hoisting (opens new window)的优化。
- shouldRecord、recordModules、recordChunks、recordHash:在 shouldRecord 返回为 true 的情况下,依次触发 recordModules、recordChunks、recordHash。
- reviveModules、beforeModuleIds、moduleIds、optimizeModuleIds、afterOptimizeModuleIds:在生成模块 Id 过程的前后触发。
- reviveChunks、beforeChunkIds、optimizeChunkIds、afterOptimizeChunkIds:在生成 Chunk id 过程的前后触发。
- beforeHash、afterHash:在生成模块与 Chunk 的 hash 过程的前后触发。
- beforeModuleAssets、moduleAsset:在生成模块产物数据过程的前后触发。
- shouldGenerateChunkAssets、beforeChunkAssets、chunkAsset:在创建 Chunk 产物数据过程的前后触发。
- additionalAssets、optimizeChunkAssets、afterOptimizeChunkAssets、optimizeAssets、afterOptimizeAssets:在优化产物过程的前后触发,例如在 TerserPlugin 的压缩代码 (opens new window)插件的执行过程中,就用到了 optimizeChunkAssets。