webpack 工作流程
本篇内容和配置基于 webpack v5.52.1 讲解
相关问题
-
webpack 工作流程是怎样的
-
webpack 在不同阶段做了什么事情
回答关键点
模块化 打包 依赖生成 工程化
webpack 是一种模块打包工具,可以将各类型的资源,例如图片、CSS、JS 等,转译组合为 JS 格式的 bundle 文件。

图片来源 webpack 官网
webpack 构建的核心任务是完成内容转化和资源合并。主要包含以下 3 个阶段:
- 初始化阶段
- 初始化参数:从配置文件、配置对象和 Shell 参数中读取并与默认参数进行合并,组合成最终使用的参数。
- 创建编译对象:用上一步得到的参数创建 Compiler 对象。
- 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等。
- 构建阶段
- 开始编译:执行 Compiler 对象的 run 方法,创建 Compilation 对象。
- 确认编译入口:进入 entryOption 阶段,读取配置的 Entries,递归遍历所有的入口文件,调用 Compilation.addEntry 将入口文件转换为 Dependency 对象。
- 编译模块(make): 调用 normalModule 中的 build 开启构建,从 entry 文件开始,调用 loader 对模块进行转译处理,然后调用 JS 解释器(acorn)将内容转化为 AST 对象,然后递归分析依赖,依次处理全部文件。
- 完成模块编译:在上一步处理好所有模块后,得到模块编译产物和依赖关系图。
- 生成阶段
知识点深入
1. webpack 初始化过程
从 webpack 项目 webpack.js 文件 webpack 方法出发,可以看到初始化过程如下:

- 将命令行参数和用户的配置文件进行合并。
- 调用 getValidateSchema 对配置进行校验。
- 调用 createCompiler 创建 Compiler 对象。
-
将用户配置和默认配置进行合并处理。
-
实例化 Compiler。
-
实例化 NodeEnvironmentPlugin。
-
处理用户配置的 plugins,执行 plugin 的 apply 方法。
-
触发 environment 和 afterEnvironment 上注册的事件。
-
注册 webpack 内部插件。
-
触发 initialize 事件。
1// lib/webpack.js 122 行 部分代码省略处理
2const create = () => {
3 if (!webpackOptionsSchemaCheck(options)) {
4 // 校验参数
5 getValidateSchema()(webpackOptionsSchema, options);
6 }
7 // 创建 compiler 对象
8 compiler = createCompiler(webpackOptions);
9};
10
11// lib/webpack.js 57 行
12const createCompiler = (rawOptions) => {
13 // 统一合并处理参数
14 const options = getNormalizedWebpackOptions(rawOptions);
15 applyWebpackOptionsBaseDefaults(options);
16 // 实例化 compiler
17 const compiler = new Compiler(options.context);
18 // 把 options 挂载到对象上
19 compiler.options = options;
20 // NodeEnvironmentPlugin 是对 fs 模块的封装,用来处理文件输入输出等
21 new NodeEnvironmentPlugin({
22 infrastructureLogging: options.infrastructureLogging,
23 }).apply(compiler);
24 // 注册用户配置插件
25 if (Array.isArray(options.plugins)) {
26 for (const plugin of options.plugins) {
27 if (typeof plugin === "function") {
28 plugin.call(compiler, compiler);
29 } else {
30 plugin.apply(compiler);
31 }
32 }
33 }
34 applyWebpackOptionsDefaults(options);
35 // 触发 environment 和 afterEnvironment 上注册的事件
36 compiler.hooks.environment.call();
37 compiler.hooks.afterEnvironment.call();
38 // 注册 webpack 内置插件
39 new WebpackOptionsApply().process(options, compiler);
40 compiler.hooks.initialize.call();
41 return compiler;
42};
2. webpack 构建阶段做了什么
在 webpack 函数执行完之后,就到主要的构建阶段,首先执行 compiler.run(),然后触发一系列钩子函数,执行 compiler.compile()。

-
在实例化 compiler 之后,执行 compiler.run()。
-
执行 newCompilation 函数,调用 createCompilation 初始化 Compilation 对象。
-
执行 _addEntryItem 将入口文件存入 this.entries(map 对象),遍历 this.entries 对象构建 chunk。
-
执行 handleModuleCreation,开始创建模块实例。
-
执行 moduleFactory.create 创建模块。
- 执行 factory.hooks.factorize.call 钩子,然后会调用 ExternalModuleFactoryPlugin 中注册的钩子,用于配置外部文件的模块加载方式。
- 使用 enhanced-resolve 解析模块和 loader 的真实绝对路径。
- 执行 new NormalModule()创建 module 实例。
-
执行 addModule,存储 module。
-
执行 buildModule,添加模块到模块队列 buildQueue,开始构建模块, 这里会调用 normalModule 中的 build 开启构建。
- 创建 loader 上下文。
- 执行 runLoaders,通过 enhanced-resolve 解析得到的模块和 loader 的路径获取函数,执行 loader。
- 生成模块的 hash。
-
所有依赖都解析完毕后,构建阶段结束。
1// 构建过程涉及流程比较复杂,代码会做省略
2
3 // lib/webpack.js 1284行
4 // 开启编译流程
5 compiler.run((err, stats) => {
6 compiler.close(err2 => {
7 callback(err || err2, stats);
8 });
9 });
10
11 // lib/compiler.js 1081行
12 // 开启编译流程
13 compile(callback) {
14 const params = this.newCompilationParams();
15 // 创建 Compilation 对象
16 const Compilation = this.newCompilation(params);
17 }
18
19 // lib/Compilation.js 1865行
20 // 确认入口文件
21 addEntry() {
22 this._addEntryItem();
23 }
24
25 // lib/Compilation.js 1834行
26 // 开始创建模块流程,创建模块实例
27 addModuleTree() {
28 this.handleModuleCreation()
29 }
30
31 // lib/Compilation.js 1548行
32 // 开始创建模块流程,创建模块实例
33 handleModuleCreation() {
34 this.factorizeModule()
35 }
36
37 // lib/Compilation.js 1712行
38 // 添加到创建模块队列,执行创建模块
39 factorizeModule(options, callback) {
40 this.factorizeQueue.add(options, callback);
41 }
42
43 // lib/Compilation.js 1834行
44 // 保存需要构建模块
45 _addModule(module, callback) {
46 this.modules.add(module);
47 }
48
49 // lib/Compilation.js 1284行
50 // 添加模块进模块编译队列,开始编译
51 buildModule(module, callback) {
52 this.buildQueue.add(module, callback);
53 }
3. webpack 生成阶段做了什么
构建阶段围绕 module 展开,生成阶段则围绕 chunks 展开。经过构建阶段之后,webpack 得到足够的模块内容与模块关系信息,之后通过 Compilation.seal 函数生成最终资源。
3.1 生成产物
执行 Compilation.seal 进行产物的封装。
- 构建本次编译的 ChunkGraph 对象,执行 buildChunkGraph,这里会将 import()、require.ensure 等方法生成的动态模块添加到 chunks 中。
- 遍历 Compilation.modules 集合,将 module 按 entry/动态引入 的规则分配给不同的 Chunk 对象。
- 调用 Compilation.emitAssets 方法将 assets 信息记录到 Compilation.assets 对象中。
- 执行 hooks.optimizeChunkModules 的钩子,这里开始进行代码生成和封装。
-
执行一系列钩子函数(reviveModules, moduleId, optimizeChunkIds 等)。
-
执行 createModuleHashes 更新模块 hash。
-
执行 JavascriptGenerator 生成模块代码,这里会遍历 modules,创建构建任务,循环使用 JavascriptGenerator 构建代码,这时会将 import 等模块引入方式替换为 webpack_require 等,并将生成结果存入缓存。
-
执行 processRuntimeRequirements,根据生成的内容所使用到的 webpack_require 的函数,添加对应的代码。
-
执行 createHash 创建 chunk 的 hash。
-
执行 clearAssets 清除 chunk 的 files 和 auxiliary,这里缓存的是生成的 chunk 的文件名,主要是清除上次构建产生的废弃内容。
3.2 文件输出
回到 Compiler 的流程中,执行 onCompiled 回调。
-
触发 shouldEmit 钩子函数,这里是最后能优化产物的钩子。
-
遍历 module 集合,根据 entry 配置及引入资源的方式,将 module 分配到不同的 chunk。
-
遍历 chunk 集合,调用 Compilation.emitAsset 方法标记 chunk 的输出规则,即转化为 assets 集合。
-
写入本地文件,用的是 webpack 函数执行时初始化的文件流工具。
-
执行 done 钩子函数,这里会执行 compiler.run() 的回调,再执行 compiler.close(),然后执行持久化存储(前提是使用的 filesystem 缓存模式)。
参考资料
- webpack5 官方文档
- acorn