likes
comments
collection
share

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

作者站长头像
站长
· 阅读数 11

今天的内容只有一个:浅析webpack5中Compiler中重要的hook调用。

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

1. 浅析Compiler 中的第一个核心hook 是 compile

此hook的入参是 params

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

1.1 查看绑定 compile 的插件的数据

进入此hook,发现只有一个监听的插件:ExternalModuleFactoryPlugin(源码篇)浅析webpack5中Compiler中重要的hook调用过程

进入 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.typethis.externals).apply(
    normalModuleFactory
   );
  });
 }
}

发现此插件 又实例化了 一个 ExternalModuleFactoryPlugin 的对象,但是 apply 方法上 传入的是 normalModuleFactory 数据。聪明的小伙伴可以想一下此处是为了啥? 进入 ExternalModuleFactoryPlugin 内部,看一下其内部实现是什么?代码如下:

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

会发现这个插件实际上也就是监听了一下 normalModuleFactory.hooks.factorize 的事件。

1.2 总结

总体来说:Compiler 里的 hooks.compile 的主要作用就是通过 ExternalModuleFactoryPlugin 监听了 normalModuleFactory.hooks.factorize 的事件。

2. 继续调试 Compiler 的 compile hook

2.1 创建 compilation 天选打工人

截图奉上 下一步的逻辑

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

具体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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

发现也是在给 compilation 绑定一堆的监听事件。

好家伙,这是要把 compilation 给累死呀,

3.2 thisCompilation 的 总结

总结:Compiler 中的 thisCompilation 的 hook,就是在疯狂的给 compilation 通过各种插件 挂各种的 监听事件,弹药准备完毕,等待一触即发。

compilation内心想法:我可真是太难了。。。)

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

4. Compiler 继续 触发 compilation 的 hook

4.1 查看绑定compilation的插件的数据

继续进行下一步 调试。

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

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

4.2 进入 ChunkPrefetchPreloadPlugin 插件

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

4.3 进入 JavascriptModulesPlugin 插件

JavascriptModulesPlugin 的代码如下:(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

继续看下面一部分代码,

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

继续进入下一个插件

4.4 进入 JsonModulesPlugin 插件

给 normalModuleFactory 绑定 监听事件(源码篇)浅析webpack5中Compiler中重要的hook调用过程

4.5 进入 AssetModulesPlugin 插件

给 normalModuleFactorycompilation 绑定 监听事件(源码篇)浅析webpack5中Compiler中重要的hook调用过程

4.6 进入 EntryPlugin 插件

这个插件 终于不绑定事件了,仅仅是 向 compilation.dependencyFactories 中塞入了一对数据。

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

4.7 进入 RuntimePlugin 插件 【核心】

这个插件的代码量是真的多,看名字分析这个插件应该是处理运行时的数据(源码篇)浅析webpack5中Compiler中重要的hook调用过程

简单看下里面的部分内容:

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

4.7 进入 InferAsyncModulesPlugin 插件

给 compilation 绑定 监听事件(源码篇)浅析webpack5中Compiler中重要的hook调用过程

4.8 直接干到最后一个 WarnCaseSensitiveModulesPlugin 插件

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

也是在给 辛苦的 compilation 的身上 挂载监听事件。

4.9 总结

总的来说, Compiler 触发 compilation 的 hook 本质上是给我们辛勤的打工人 compilation对象 的 不同的数据处理阶段 绑定不同的插件。

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

5 继续 Compiler 的下一个hook make

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

5.1 查看绑定make的插件的数据

nice 的很,这个 hook 仅仅 有一个叫 EntryPlugin 的插件进行绑定。(源码篇)浅析webpack5中Compiler中重要的hook调用过程

5.2 进入 EntryPlugin 插件 【 compilation 对象解析的开始】

核心代码截图如下:

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

你会发现这个插件就是调用了 compilation.addEntry 的方法,没有做其他逻辑。那就开始分析此函数的入参,context, dep, options。 查看一下入参的数据,如下图(源码篇)浅析webpack5中Compiler中重要的hook调用过程

你就会显而易见的发现 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 对象的数据 都有啥:

(源码篇)浅析webpack5中Compiler中重要的hook调用过程webpack.config.js 配置如下:(源码篇)浅析webpack5中Compiler中重要的hook调用过程

是不是看到了,熟悉的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函数

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

5.4 进入 this.addModuleTree 的方法

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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

5.5 进入this.handleModuleCreation 的方法

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

又进入 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 中添加了一个数据。然后就么有然后了????

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

老规矩:总结

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

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

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

(源码篇)浅析webpack5中Compiler中重要的hook调用过程

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