跳到主要内容

04-浅析webpack5中Compiler中重要的hook调用过程

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

img

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

此 hook 的入参是 params

img

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

进入此 hook,发现只有一个监听的插件:ExternalModuleFactoryPluginimg

进入 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 内部,看一下其内部实现是什么?代码如下:

img

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

1.2 总结

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

2. 继续调试 Compilercompile hook

2.1 创建 compilation 天选打工人

截图奉上 下一步的逻辑

img

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

img

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

img

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

img

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

img

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

img

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

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

3.2 thisCompilation 的 总结

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

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

img

4. Compiler 继续 触发 compilation 的 hook

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

继续进行下一步 调试。

img

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

img

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

4.2 进入 ChunkPrefetchPreloadPlugin 插件

img

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

img

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

4.3 进入 JavascriptModulesPlugin 插件

JavascriptModulesPlugin 的代码如下: img

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

继续看下面一部分代码,

img

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

继续进入下一个插件

4.4 进入 JsonModulesPlugin 插件

normalModuleFactory 绑定 监听事件 img

4.5 进入 AssetModulesPlugin 插件

normalModuleFactorycompilation 绑定 监听事件 img

4.6 进入 EntryPlugin 插件

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

img

4.7 进入 RuntimePlugin 插件 【核心】

这个插件的代码量是真的多,看名字分析这个插件应该是处理运行时的数据 img

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

img

img

4.7 进入 InferAsyncModulesPlugin 插件

compilation 绑定 监听事件 img

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

img

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

4.9 总结

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

img

5 继续 Compiler 的下一个 hook make

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

img

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

nice 的很,这个 hook 仅仅 有一个叫 EntryPlugin 的插件进行绑定。 img

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

核心代码截图如下:

img

你会发现这个插件就是调用了 compilation.addEntry 的方法,没有做其他逻辑。那就开始分析此函数的入参,context, dep, options。 查看一下入参的数据,如下图 img

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

img webpack.config.js 配置如下: img

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

img

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

img

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

5.4 进入 this.addModuleTree 的方法

img

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

5.5 进入this.handleModuleCreation 的方法

img

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

img

老规矩:总结

  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 中的 任务调度中,进行讲解,敬请期待。

img

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