【源码】webpack5 编译主流程
本文将向大家介绍当我们执行webpack命令的时候,整个流程主要做了哪些事情,用于更好的理解主流程相关节点。
webpack版本
- 5.90.3
一、执行webpack命令
1 |
|
简单写了个webpack.config.js文件,执行 webpack –config webpack.config.js 命令实际上是通过 webpack-cli 实现的。在 webpack-cli 中,调用了 webpack 方法,并传入了一个 callback 函数,用于执行终端的后续操作。
1 |
|
在 webpack 方法中,根据是否传入了 callback 函数,分别进行了不同的处理。如果传入了 callback,则创建编译器实例并执行编译过程,最后调用 callback 函数。如果没有传入 callback,则仅创建编译器实例。
1 |
|
总的来说,webpack-cli 的作用是调用 webpack 方法,并传入相应的配置选项,来执行编译过程,并在需要时执行后续操作。
二、创建编译器实例:createCompiler()
1 |
|
它接受一个参数 rawOptions,该参数是原始的Webpack配置选项。该函数的作用是根据传入的配置选项创建并配置一个Webpack编译器实例,并返回该实例。下面将逐行讲解。
getNormalizedWebpackOptions
1 |
|
对原始选项进行规范化处理,得到标准化的 webpack 配置选项对象,并将其赋值给 options 变量。举例如下:
这里仅仅只是简单的赋值,并没有进行相应的必填校验
new Compiler();
1 |
|
通过Compiler类创建一个新的编译器实例
plugin.apply();
1 |
|
遍历插件选项,如果插件是一个函数,那么调用该函数。如果插件是一个对象,则调用它的 apply 方法。
applyWebpackOptionsDefaults
1 |
|
applyWebpackOptionsDefaults的作用是应用 webpack 配置选项的默认值,以确保配置对象中包含必要的属性,并且没有缺失或不合法的值。
例句源码中,第一行
F函数的作用就是为了当它的值未定义时,则调用传入的工厂函数进行初始化
environment和afterEnvironment
1 |
|
执行了两个钩子函数:环境准备和环境准备完成。
WebpackOptionsApply
1 |
|
应用 webpack 配置选项中的各种配置项,包括入口、输出路径、加载器、插件等,准备相应的方法去实现。
initialize
1 |
|
调用编译器实例的 initialize 钩子,执行相关的钩子回调函数。这个钩子用于在编译器初始化之后执行一些操作。
至此,我们的compiler实例对象准备好了;接下来再来看下new Compiler()究竟做了什么。
三、编译器构造函数:new Compiler()
hooks初始化
在Compiler的构造函数中,初始化了一个hooks对象,准备收集用户注册的各种插件。
hooks注册
或许你已经发现,在初始化hooks对象时,使用SyncHook或者AsyncSeriesHook等创建了一个对象用于初始化。其实它来自鼎鼎大名的 Tapable 。插件的注册其实也就是一种发布订阅模式。更多事件类型如下:
三、compiler启动编译
一切准备就绪。等到被调用run方法时,才真正开始进入编译阶段。在run方法中,先执行了beforeRun(在 webpack 开始编译前执行。)、run(在 webpack 正式开始编译过程时执行。)两个钩子函数,接着开始调用compile方法执行编译,并传入了一个编译完成函数onCompiled。
Compile.compile()
先执行了beforeCompile(在 webpack 开始编译模块前执行。)、compile(在 webpack 开始编译模块时执行。)两个钩子函数,接着通过const compilation = this.newCompilation(params);
创建了一个 compilation
对象。
接着又执行了 make(在编译器完成创建新的编译(Compilation)实例时执行。)、finishMake(在编译器完成创建新的编译实例后触发。)两个钩子。
最后在完成编译时执行了 afterCompile(编译后钩子,在编译过程完成后执行。) 钩子函数,并调用了传入的回调方法onCompiled。
在这个方法中,
onCompiled
在onCompiled方法中,Webpack 会调用 shouldEmit 钩子函数来判断是否需要输出编译后的文件。如果 shouldEmit 返回 true,则表示需要输出文件;如果返回 false,则表示不需要输出文件,webpack 将直接跳过输出阶段,节省了不必要的 IO 操作。
例如当我们启动dev模式时,则不会生成文件;启动build模式则会生成。
如果不需要生成文件,直接执行了 done 钩子并调用 finalCallback 回调函数,结束编译过程。
如果需要生成文件,调用 emitAssets 方法生成资源文件。
finalCallback
这段代码实际上是 webpack 编译过程中的最终回调函数,在编译完成后执行一些收尾工作,并触发 afterDone(在 done 钩子函数执行完毕后执行。) 钩子函数,用于通知编译过程的状态。
生成文件:emitAssets
在这里我们见到了 emit
(生成资源到输出目录之前触发)钩子函数,经常写插件的人应该很熟悉。接着开始调用mkdirp方法把文件输出到指定目录。
而在emitFiles方法中,主要做了这几件事情。
首先检查是否有错误,如果有错误则立即回调返回错误。
1
if (err) return callback(err);
获取编译好的资源文件列表 assets。
1
const assets = compilation.getAssets();
遍历资源文件列表,对每个资源文件进行处理:
1
asyncLib.forEachLimit()
所有资源文件处理完成后,清除临时数据,并触发钩子函数 afterEmit。
至此,一次编译主流程的线路已经完成。
四、扩展知识
1、如何创建一个插件?
- 一个 JavaScript 命名函数或 JavaScript 类。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调。
类写法
1 |
|
函数写法
1 |
|
2、compile和compilation
compile:
- compile 是指整个编译过程的开始,也可以指代编译器的一个实例。在Webpack中,当你调用编译器的 run 方法时,就会触发一次编译过程,这个过程就可以称为一次 compile。compile 是一个动词,表示执行编译过程的行为。
compilation:
- compilation 则是指每一次编译过程中生成的编译对象。每次调用编译器的 run 方法都会生成一个 compilation 对象,它包含了当前编译过程的所有信息,包括输入的模块、输出的资源、编译过程中的各种事件等。compilation 是一个名词,表示编译过程中产生的结果或对象。
综上所述,compile 是一个动词,表示执行编译过程的行为,而 compilation 是一个名词,表示编译过程中生成的结果或对象。在Webpack中,每次执行编译过程都会生成一个新的 compilation 对象,用于跟踪和管理当前编译过程的所有信息。
简单理解,当我们启用 webpack watch时,会启动一个compile和compilation;而当过程中又有文件变更,则会再产生一次compilation,而compile则是最开始创建的那一个。
3、主要钩子函数
entryOption:
- 当Webpack开始解析配置文件中的 entry 配置项时触发。可以用于动态修改 entry 配置。
afterPlugins:
- 在Webpack加载完插件之后触发,但在Webpack配置项中的其他选项开始解析之前。
afterResolvers:
- 在Webpack初始化完解析器(resolvers)之后触发。可以用于对解析器进行进一步的配置或自定义。
beforeRun:
- 在Webpack开始编译前触发,异步地编译前执行。
run:
- 在Webpack开始编译前触发,同步地编译前执行。
beforeCompile:
- 在Webpack即将开始编译模块前触发,这时候已经有了文件的监听。
compile:
- 在Webpack开始编译模块前触发。
thisCompilation:
- 当一个新的 compilation 创建时触发。
compilation:
- 在一个 compilation 对象被创建后触发,这是一个异步的钩子。
make:
- 在一个新的 compilation 开始时触发。
afterCompile:
- 编译完成后触发,可以拿到编译后的 compilation 对象。
shouldEmit:
- 在生成资源到 output 目录前触发,可以控制是否生成资源。
emit:
- 在生成资源到 output 目录时触发。
afterEmit:
- 在资源被输出到 output 目录后触发。
done:
- 整个编译过程完成后触发,包括成功完成编译或发生错误时。