(源码篇)浅析webpack5中Compiler中重要的hook调用过程
今天的内容只有一个:浅析webpack5中Compiler中重要的hook调用。

1. 浅析Compiler 中的第一个核心hook 是 compile
此hook的入参是 params

1.1 查看绑定 compile 的插件的数据
进入此hook,发现只有一个监听的插件:ExternalModuleFactoryPlugin。
进入 ExternalModuleFactoryPlugin的插件中,代码如下:
class ExternalsPlugin {
/**
* @param {string | undefined} type default external type
* @param {Externals} externals externals config
*/
constructor(type, externals) {
this.type = type;
this.externals = externals;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compile.tap("ExternalsPlugin", ({ normalModuleFactory }) => {
new ExternalModuleFactoryPlugin(this.type, this.externals).apply(
normalModuleFactory
);
});
}
}
发现此插件 又实例化了 一个 ExternalModuleFactoryPlugin 的对象,但是 apply 方法上 传入的是 normalModuleFactory 数据。聪明的小伙伴可以想一下此处是为了啥? 进入 ExternalModuleFactoryPlugin 内部,看一下其内部实现是什么?代码如下:

会发现这个插件实际上也就是监听了一下 normalModuleFactory.hooks.factorize 的事件。
1.2 总结
总体来说:Compiler 里的 hooks.compile 的主要作用就是通过 ExternalModuleFactoryPlugin 监听了 normalModuleFactory.hooks.factorize 的事件。
2. 继续调试 Compiler 的 compile hook
2.1 创建 compilation 天选打工人
截图奉上 下一步的逻辑

会发现走到了 newCompilation 方法,通过规范的命名就知道,此方法是创建一个此次编译构建用的 compilation 一次性对象,注意此处说的是一次性对象,表述上可能存在差异,给兄弟们放上解释。【我感觉可以简单理解为一次完整编译构建过程中的数据载体】。

具体newCompilation代码如下:
newCompilation(params) {
const compilation = this.createCompilation(params); // 下方
compilation.name = this.name;
compilation.records = this.records;
// 调用 thisCompilation hook
this.hooks.thisCompilation.call(compilation, params);
// 调用 compilation hook
this.hooks.compilation.call(compilation, params);
return compilation;
}
createCompilation(params) {
this._cleanupLastCompilation();
return (this._lastCompilation = new Compilation(this, params));
}
核心也就是在这里了,通过 createCompilation 传递 compiler 和 params参数,创建 Compilation 的实例对象。
创建完毕以后,开始 传入 compilation 和 params 调用 hooks.thisCompilation的hook了。
下一步就是开始调试 thisCompilation 的 hook。
3. Compiler 创建了 compilation,开始调用 thisCompilation 的 hook
3.1 查看绑定 compilation 的插件的数据
继续 调试进入此hook

这个hook竟然有 10个 插件监听它,先进入第一个插件 ArrayPushCallbackChunkFormatPlugin

发现这个插件都是在给 compilation 绑定一下 监听事件,并没有做什么实际的操作,继续下一个插件JsonpChunkLoadingPlugin。

进入以后,发现也是在进行给 compilation 绑定监听事件。直接进入 最后一个插件ResolverCachePlugin

发现也是在给 compilation 绑定一堆的监听事件。
好家伙,这是要把 compilation 给累死呀,
3.2 thisCompilation 的 总结
总结:Compiler 中的 thisCompilation 的 hook,就是在疯狂的给 compilation 通过各种插件 挂各种的 监听事件,弹药准备完毕,等待一触即发。
(compilation内心想法:我可真是太难了。。。)

4. Compiler 继续 触发 compilation 的 hook
4.1 查看绑定compilation的插件的数据
继续进行下一步 调试。

我直接疯了啊,这Compiler 的 compilation 的 hook 竟然绑定了 54 个监听的插件。
我 直接疯掉。而大家作为优秀的 程序员,那不就是多点54次调试吗? 进入第一个插件 ChunkPrefetchPreloadPlugin
4.2 进入 ChunkPrefetchPreloadPlugin 插件

这又是给 compilation 对象 【注意不是hook名称】,绑定了一身的 监听事件。

继续看下一个插件的,下一个插件是 JavascriptModulesPlugin
4.3 进入 JavascriptModulesPlugin 插件
JavascriptModulesPlugin 的代码如下:
看到了上面的一部分,compilation 对象终于开心了,终于不仅仅是给我绑定监听事件了,这次轮到了 normalModuleFactory,心里美滋滋。
继续看下面一部分代码,

去求吧,compilation 对象还是逃不过被绑定监听事件的命运,【注意此处的hook名称是不同的】。
继续进入下一个插件
4.4 进入 JsonModulesPlugin 插件
给 normalModuleFactory 绑定 监听事件
4.5 进入 AssetModulesPlugin 插件
给 normalModuleFactory和compilation 绑定 监听事件
4.6 进入 EntryPlugin 插件
这个插件 终于不绑定事件了,仅仅是 向 compilation.dependencyFactories 中塞入了一对数据。

4.7 进入 RuntimePlugin 插件 【核心】
这个插件的代码量是真的多,看名字分析这个插件应该是处理运行时的数据
简单看下里面的部分内容:


4.7 进入 InferAsyncModulesPlugin 插件
给 compilation 绑定 监听事件
4.8 直接干到最后一个 WarnCaseSensitiveModulesPlugin 插件

也是在给 辛苦的 compilation 的身上 挂载监听事件。
4.9 总结
总的来说, Compiler 触发 compilation 的 hook 本质上是给我们辛勤的打工人 compilation对象 的 不同的数据处理阶段 绑定不同的插件。

5 继续 Compiler 的下一个hook make
走完 this.newCompilation(params); 的调用流程后,下一步就是调用 Compiler 的 make 的hook了。

5.1 查看绑定make的插件的数据
nice 的很,这个 hook 仅仅 有一个叫 EntryPlugin 的插件进行绑定。
5.2 进入 EntryPlugin 插件 【 compilation 对象解析的开始】
核心代码截图如下:

你会发现这个插件就是调用了 compilation.addEntry 的方法,没有做其他逻辑。那就开始分析此函数的入参,context, dep, options。 查看一下入参的数据,如下图
你就会显而易见的发现 dep 这个是主角了,那 dep 又是由 const dep = EntryPlugin.createDependency(entry, options); 创建的,查看 createDependency 静态方法:
static createDependency(entry, options) {
// 创建了 EntryDependency 继承自 ModuleDependency 继承自 Dependency (抽象类)
const dep = new EntryDependency(entry);
// TODO webpack 6 remove string option
dep.loc = { name: typeof options === "object" ? options.name : options };
return dep;
}
直接看 这个 dep 对象的数据 都有啥:
webpack.config.js 配置如下:
是不是看到了,熟悉的webpack 中的 entry 的路径 和 此 dep 对象的 request 属性是一致的呢?
5.3 进入 compilation 中 查看,addEntry 方法
上代码
addEntry(context, entry, optionsOrName, callback) {
console.log("add entry");
// TODO webpack 6 remove
const options =
typeof optionsOrName === "object"
? optionsOrName
: { name: optionsOrName };
this._addEntryItem(context, entry, "dependencies", options, callback);
}
此函数仅仅是做了数据处理,真正干活的还在 this._addEntryItem 函数中,进入 this._addEntryItem函数

你会发现此函数 调用了 this.hooks.addEntry【注意此时的 this 指的的是 compilation 对象】,进入此 hook

并没有插件 监听它,直接进入下一行代码:调用this.addModuleTree 方法。
5.4 进入 this.addModuleTree 的方法

处理完数据以后,又进入一个 this.handleModuleCreation 的方法。
5.5 进入this.handleModuleCreation 的方法

又进入 this.factorizeModule 的方法
5.6 进入 this.factorizeModule 的方法
// Workaround for typescript as it doesn't support function overloading in jsdoc within a class
Compilation.prototype.factorizeModule = /** @type {{
(options: FactorizeModuleOptions & { factoryResult?: false }, callback: ModuleCallback): void;
(options: FactorizeModuleOptions & { factoryResult: true }, callback: ModuleFactoryResultCallback): void;
}} */ (
function (options, callback) {
console.log("add entry to factorize Queue, real job start");
this.factorizeQueue.add(options, callback);
}
);
你会发现它是在Compilation 的原型上绑定的一个方法,其主要的作用就是 向 factorizeQueue 中添加了一个数据。然后就么有然后了????

老规矩:总结
Compiler里的hooks.compile的主要作用就是通过ExternalModuleFactoryPlugin监听了normalModuleFactory.hooks.factorize的事件。Compiler的compilehook 传递compiler 和 params参数 给createCompilation方法,创建Compilation的实例对象compilationCompiler中的thisCompilation的 hook,就是在疯狂的给compilation对象 通过各种插件 挂各种的 监听事件,弹药准备完毕,等待一触即发Compiler中的compilation的 hook 本质上也是给我们辛勤的打工人compilation对象的不同的数据处理阶段,绑定不同的插件。Compiler中的make的hook,主要是 通过EntryPlugin插件,解析我们传入的entry属性,并调用compilation.addEntry的方法,进而通过一系列的数据处理,最后将数据塞入到compilation的factorizeQueue中。
在一个类似 队列的数据中添加了,就没有然后了吗?到底怎么开始启动的呢?怎么没有见到 compilation 调用之前的hook呢?
其实上面的疑问都会在下一篇的 webpack5 中的 任务调度中,进行讲解,敬请期待。

如果有忍不住的小伙伴们,也可以自己去调试一下webpack5 相关的源码,欢迎一起学习交流。兄弟们下篇文章再见。
转载自:https://juejin.cn/post/7209553403810742330