付俊奎

V1

2023/03/12阅读:17主题:兰青

(源码篇)浅析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. 继续调试 Compilercompile 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的插件的数据

继续进行下一步 调试。

我直接疯了啊,这Compilercompilation 的 hook 竟然绑定了 54 个监听的插件。

我 giao哥 直接疯掉。而我和大家作为优秀的 程序员,那不就是多点54次调试吗? 进入第一个插件 ChunkPrefetchPreloadPlugin

4.2 进入 ChunkPrefetchPreloadPlugin 插件

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

继续看下一个插件的,下一个插件是 JavascriptModulesPlugin

4.3 进入 JavascriptModulesPlugin 插件

JavascriptModulesPlugin 的代码如下:

看到了上面的一部分,compilation 对象终于开心了,终于不仅仅是给我绑定监听事件了,这次轮到了 normalModuleFactory,心里美滋滋。

继续看下面一部分代码,

去求吧,compilation 对象还是逃不过被绑定监听事件的命运,【注意此处的hook名称是不同的】。

继续进入下一个插件

4.4 进入 JsonModulesPlugin 插件

normalModuleFactory 绑定 监听事件

4.5 进入 AssetModulesPlugin 插件

normalModuleFactorycompilation 绑定 监听事件

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); 的调用流程后,下一步就是调用 Compilermake 的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 = { nametypeof 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 中添加了一个数据。然后就么有然后了????

老规矩:总结

  1. Compiler 里的 hooks.compile 的主要作用就是通过 ExternalModuleFactoryPlugin 监听了 normalModuleFactory.hooks.factorize 的事件。
  2. Compilercompile hook 传递 compiler 和 params参数 给 createCompilation 方法,创建 Compilation 的实例对象 compilation
  3. Compiler 中的 thisCompilation 的 hook,就是在疯狂的给 compilation对象 通过各种插件 挂各种的 监听事件,弹药准备完毕,等待一触即发
  4. Compiler 中的 compilation 的 hook 本质上也是给我们辛勤的打工人 compilation对象的不同的数据处理阶段,绑定不同的插件。
  5. Compiler中的 make 的hook,主要是 通过 EntryPlugin 插件,解析我们传入的entry属性,并调用 compilation.addEntry 的方法,进而通过一系列的数据处理,最后将数据塞入到 compilationfactorizeQueue 中。

在一个类似 队列的数据中添加了,就没有然后了吗?到底怎么开始启动的呢?怎么没有见到 compilation 调用之前的hook呢?

其实上面的疑问都会在下一篇的 webpack5 中的 任务调度中,进行讲解,敬请期待。

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

分类:

前端

标签:

前端

作者介绍

付俊奎
V1