前言

上文中我们所提及到的webpack创建了一个Compiler对象,由它来进行相关的打包任务动作的启动等,而且作为所有的插件所共同访问的到的一个编译器对象,😕 那么,这个Compiler是什么呢??它做了哪些事情呢?

Compiler的组成

Compiler

Compiler是什么?执行过程是怎样的?

Compiler 模块是 webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。
首先,先来看一下 👇 的一副Compile的run方法的执行过程所触发的钩子函数,以及都有哪些插件在对应的钩子容器中添加了各自的钩子函数动作:
Compiler的run方法触发的钩子函数
我们可以清楚地了解到run方法的执行过程, 👇 将一一来分析每一个节点过程

1、触发beforeRun方法

在开始执行一次构建之前调用,compiler.run 方法开始执行后立刻进行调用

描述
传递参数 Compiler对象
调用方式 AsyncSeriesHook
相关插件 ProgressPlugin.jsNodeEnvironmentPlugin.js

两个插件,主要做的动作有:

  1. ProgressPlugin负责进行进度的输出信息;
  2. NodeEnviromentPlugin负责日志环境以及相关的fs环境的赋值操作等!

2、进入run方法

开始进入run动作,目前webpack中只有ProgressPlugin触发了对应的钩子函数!上面已经具体介绍过,这里就不再重复阐述

3、readRecords方法

读取之前是否有缓存过的相关记录动作,该方法对应的介绍如下:

描述
传递参数 当前调用的plugin对象,以及一个回调函数,作为异步回调通知的动作
调用方式 AsyncSeriesHook
相关插件 SyncModuleIdsPlugin.js

插件SyncModuleIdsPlugin做的主要事情有:
👉 获取Compiler对象中的intermediateFileSystem属性,用来从之前缓存中获取已经存储的信息

4、创建模块工厂对象normalModuleFactory

创建编译所需的参数

将创建好的NormalModuleFactory对象缓存到_lastNormalModuleFactory属性中,触发NormalModuleFactory对象被创建的钩子动作

描述
传递参数 当前调用的normalModuleFactory对象
调用方式 AsyncSeriesHook
相关插件 IgnorePlugin.jsNormalModuleReplacement.js

具体的过程描述如下:
👇 创建一个NormalModuleFactory对象,并触发hooks.normalModuleFactory钩子方法,将normalModuleFactory作为参数传递过去!

IgnorePlugin所做的事情:
IgnorePlugin间接对NormalModuleFactory以及ContextModuleFactory设置钩子监听
所做的事情主要是间接设置对normalModuleFactory.hooks.beforeResolve以及contextModuleFactory.hooks.beforeResolve钩子函数的监听,当这两个钩子容器触发的时候,调用自身的checkIgnore方法

NormalModuleReplacement所做的事情:
👉 针对NormalModuleFactory.hooks.beforeResolve以及afterResolve设置监听,当由NormalModuleFactory来创建的时候,触发该动作,实现采用正则匹配的方式,来替换即将导入的依赖文件,比如将文件A替换为文件B等骚操作!

5、创建上下文工厂对象contextModuleFactory

6、进入beforeCompile方法

通过 第4、5步中创建的params(包含有:normalModuleFactory、contextModuleFactory)参数,进入beforeCompile钩子容器,将params作为参数来进行触发beforeCompile方法

描述
传递参数 {normalModuleFactory, contextModuleFactory}
调用方式 AsyncSeriesHook
相关插件 DllReferencePlugin.jsLazyCompilationPlugin.js

插件DllReferencePlugin的执行过程如下:
beforeCompile阶段DllReferencePlugin的执行过程
简单描述: 👉 读取通过webpack.config.js传递进来的插件配置,并获取该配置中的manifest属性,将这个属性的值进行解析获取,并最后将解析的结果值存储在当前对象中的_compilationData属性中,以便于后续使用

插件LazyCompilationPlugin的执行过程如下(内部插件):
LazyCompilationPlugin的实验性使用
简单描述: 👉 该插件是一个实验性的插件,由webpack.config.js通过experiments.lazyCompilation配置属性来进行配置,目的是使用动态导入的方式来编译!

7、触发compile方法

触发compile方法,并传递{normalModuleFactory, contextModuleFactory}对象作为参数

描述
传递参数 {normalModuleFactory, contextModuleFactory}
调用方式 AsyncSeriesHook
相关插件 DllReferencePlugin.js、DelegatedPlugin.js、ExternalsPlugin.js

插件DllReferencePlugin的执行过程如下:
简单描述: 👉 获取从上一步(beforeCompile)阶段所缓存下来的_compilationData属性中的值,并结合当前插件所传递的dll配置参数,组装成为一个externals对象,然后触发ExternalModuleFactoryPlugin插件以及DelegatedModuleFactoryPlugin插件,并同时以NormalModuleFactory作为参数来进行传递!

👽 而这里的ExternalModuleFactoryPlugin插件,则是对NormalModuleFactory.hooks.factorize设置监听,当监听被触发时,将从额外的插件中将dll给单独打包!

👽 这里的DelegatedModuleFactoryPlugin插件,则是根据Dll配置参数的前缀属性scope,来对应分别对NormalModuleFactory.hooks.factorize或者是module钩子容器添加监听函数,然后返回最终的DelegatedModule模块交给NormalModuleFactory模块的对应钩子函数去执行操作,它是实现从dll以及externals中外部导入或者剔除打包的具体实现!

8、thisCompilation创建一个Compilation对象,并触发hooks.compilation方法

首先,先看一下Compilation对象的创建过程,如下图所示:
Compiler创建Compilation对象
通过创建一Compilation对象,并将其作为参数,触发钩子容器中的:thisCompilation以及compilation方法
关于这里具体调用的那些插件的动作,可见

9、开始make进行打包动作,完成打包finishMake

make阶段实现module的自我build动作!!!
Compiler执行make动作并触发真正的编译动作
从这里开始,进入编译阶段,具体可以浏览 EntryOptionPlugin插件

然后是finishMake阶段,一般来说,该阶段暂无具体的插件干预,除了ProvideSharedPlugin,在完成finishMake阶段之后,将自动执行compilation.finish()以及compilation.seal()完成模块的编译以及内容的生成和优化动作,具体可分别查看
compilation.finish阶段以及compilation.seal阶段

10、收尾工作afterCompile

收尾工作主要由AutomaticPrefetchPlugin来完成,通过该插件,获取编译阶段的module,并将其进行缓存下来

11、是否通知shouldEmit

此阶段完成Compiler.run(callback)方法的执行,并进入其callback方法(onCompiled)的回调,
随后将调用自身的emitAssets()方法来根据compilation所生成的module来打包输出文件!
并触发compiler.hooks.emit钩子方法,告知即将要结果字符串写入到文件中了,在这个阶段,可以在文件被输出时,拦截获取其文件以及对应的内容,如下插件所示:

1
2
3
4
5
6
7
8
9
10
11
12
class OutputLogPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('OutputLogPlugin', (compilation, callback) => {
console.info(compilation.getAssets());
callback && callback();
});
}
}
module.exports = OutputLogPlugin;

emit阶段获取文件以及其内容
🌠 通过compilation.getAssets()可获取即将要生成的文件内容!

12、完成所有工作done以及afterDone

此阶段仅有在设置了对compilation.hooks.needAdditionalPass进行了监听并返回了true结果的情况下,才会触发该动作!
done在 compilation 完成时执行