一、前文回顾
上文对 Compiler.prototype.compile 方法进行了由浅入深的解析,compile 则是 compiler 对象真正处理编译工作的地方,因此 compiler.compile 方法的内部就是整个编译过程的调度图。compiler.compile 方法主要做了以下工作:
- 调用 compiler.newCompilationParams() 方法为创建 compilation 实例准参数;
- compiler.hooks.beforeCompile 钩子;
- 触发 compiler.hooks.compile 钩子,参数同样传入的是第一步获取的 params 对象;
- 执行 compiler.newCompilation(params) 创建 compilation 实例;
- 传入 compilation 对象触发 compiler.hooks.make 钩子;
- 接着介绍了订阅在 compiler.hooks.make 上的 EntryPlugin,同时介绍了 EntryPlugin 为整个构建投料的重要意义。
从这一篇开始,以后相当一段时间的重点将会转移到 compilation 对象上。具体的工作将要展开了,比如模块的编译、执行 loader,创建模块图等等。
今天我们先来了解一下 compilation.addEntry 及 compilation 实例对象的创建过程;
二、compilation 对象
compilation 对象实例化自 Compilation 类型。实例化的调用是由前面的 compiler.newCompilation() 方法完成。
compilation 对象则是 webpack 构建编译具体过程的实施者,说白了就是干活儿的了。
2.1 Compilation 类型
-
定义位置:webpack/lib/Compilation.js;
-
构造函数参数:
- 2.1 compiler: Compiler 类型实例,创建当前 compilation 的 compiler 对象;
- 2.2 params: 创建 compilation 所需的参数对象,当然,这里我们是很清楚的,这里面包含的是 normalModuleFactory 和 contextModuelFactory 这两个类型模块的工厂;
-
关键方法:
- 3.1 addEntry:添加入口
- 3.2 seal: 优化 module、chunk
- 3.3 ....
2.2 构造函数
构造函数中的内容很多,初始化了相当多的属性,有很多暂时用不到,这里我们就先忽略它,我们先来看看整体的结构,主要包括了以下内容的初始化:
- compilation.hooks,注册 compilation 的 hooks 对象;
- 初始化 compilation.mainTemplate 属性;
- 初始化 compilation.chunkTemplate 属性;
- 初始 compilation.runtimeTemplate 属性;
- 初始化 compilation.processDependenciesQueue;
- 初始化 compilation.addModuleQueue;
- 初始化 compilation.factorizeQueue;
- 初始化 compilation.buildQueue;
有关各个属性的作用,我们下面用小标题进行单独讲解!
js
class Compilation {
constructor(compiler, params) {
// ....
this.hooks = Object.freeze({ /**/ })
this.mainTemplate = new MainTemplate(this.outputOptions, this);
this.chunkTemplate = new ChunkTemplate(this.outputOptions, this);
this.runtimeTemplate = new RuntimeTemplate(
this,
this.outputOptions,
this.requestShortener
);
this.moduleTemplates = {
javascript: new ModuleTemplate(this.runtimeTemplate, this)
};
this.processDependenciesQueue = new AsyncQueue({
name: "processDependencies",
parallelism: options.parallelism || 100,
processor: this._processModuleDependencies.bind(this)
});
this.addModuleQueue = new AsyncQueue({
name: "addModule",
parent: this.processDependenciesQueue,
getKey: module => module.identifier(),
processor: this._addModule.bind(this)
});
this.factorizeQueue = new AsyncQueue({
name: "factorize",
parent: this.addModuleQueue,
processor: this._factorizeModule.bind(this)
});
this.buildQueue = new AsyncQueue({
name: "build",
parent: this.factorizeQueue,
processor: this._buildModule.bind(this)
});
}
getCache(name) {
return this.compiler.getCache(name);
}
addModule(module, callback) {
this.addModuleQueue.add(module, callback);
}
_addModule(module, callback) {}
buildModule(module, callback) {
this.buildQueue.add(module, callback);
}
_buildModule(module, callback) {}
processModuleDependencies(module, callback) {}
handleModuleCreation(
{
factory,
dependencies,
originModule,
contextInfo,
context,
recursive = true,
connectOrigin = recursive
},
callback
) {}
_handleModuleBuildAndDependencies(originModule, module, recursive, callback) {
this.processDependenciesQueue.add(module, callback);
}
_factorizeModule(
{
currentProfile,
factory,
dependencies,
originModule,
factoryResult,
contextInfo,
context
},
callback
) {}
addModuleChain(context, dependency, callback) {
return this.addModuleTree({ context, dependency }, callback);
}
addModuleTree({ context, dependency, contextInfo }, callback) { }
addEntry(context, entry, optionsOrName, callback) {
// TODO webpack 6 remove
const options =
typeof optionsOrName === "object"
? optionsOrName
: { name: optionsOrName };
this._addEntryItem(context, entry, "dependencies", options, callback);
}
_addEntryItem(context, entry, target, options, callback) {}
finish(callback) {}
seal(callback) {}
createModuleHashes() {}
emitAsset(file, source, assetInfo = {}) {}
}
2.2.1 this.hooks
这里声明的是 compilation 的 hooks 对象,前面我们介绍过 compiler 的钩子注册,当时介绍 compiler介 绍的的很详细。但是这里有个大概的印象就行,compilation 的钩子贯穿了构建的全过程,即从 module 到 chunk 的全过程。
这个 hooks 包含了 addEntry、finishMoudle、optimize、optimizeModule、seal、unseal、 beforeChunk、afterChunk 等钩子;
2.2.2 this.mainTemplate
ompilation.mainTemplate
是用于渲染入口 Chunk 的模板。compilation.mainTemplate
的作用是根据 Chunk 生成最终文件,主要有三个步骤:
- 模板
hash
更新; - 模板渲染
chunk
; - 生成文件;
2.2.3 this.chunkTemplate
ompilation.ChunkTemplate
用于动态引入的非入口 Chunk 的渲染模板。
webpack 构建过程中会将输入的 模块
分割成多个代码块 Chunks
。每个 chunk 都是一个独立块,包含了相关的模块代码和运行时代码(或部分)。compilation.ChunkTemplate
提供了一种方式来定义或修改代码块的生成相关逻辑;
2.2.4 this.runtimeTemplate
在 webpack 中,compilation.runtimeTemplate
用于生成运行时模板。运行时模板是用于生成最终输出文件的一部分。
所谓 webpack 运行时就是大家口中的 runtime 代码,就是 webpack 内部实现的模块加载、导出、异步加载等用于组织 webpack 构建所得的bundle 和 chunk 正确执行的代码;以下是部分大家常用的 runtime 代码:
- webpack_require 方法 webpack 运行时的主要方法,其作用创建并缓存 module 对象()后,执行这个 module 中的代码;
js
function __webpack_require__(moduleId) {}
- webpack_require.e 方法 用于加载额外 chunk 的函数,比如按需加载的 chunk,这个里面就会有创建 script 标签然后去加载代码的具体逻辑;
js
__webpack_require__.e = function requireEnsure(chunkId) {};
- webpack_require.m 属性 暴露这个 runtime 接收到的 modules 对象;
js
__webpack_require__.m = modules;
- webpack_require.d 态方法 在模块对象(module 上面__webpack_require__ 中创建的 module 对象,下同)的 exports 对象上增加属性,以 getter 的形式定义导出(就是实现你代码中的通过 export 导出一个变量/常量/函数等)
js
__webpack_require__.d = function(exports, name, getter) {};
- webpack_require.r 静态方法 在模块对象(module) 增加 __esModule 属性,用于标识这个模块是个 ES6 模块
js
__webpack_require__.r = function(exports) {};
更多内容可以看我之前写过的一篇文章 webpack 的输出文件竟然这么妙!
2.2.5 this.moduleTemplate
webpack 中,ModuleTemplate
是用于渲染 chunk 中的 module 的模板。它与 MainTemplate
和 ChunkTemplate
联用,在实例化 Compilation
对象时被创建,分别对应入口 chunk、动态引入的非入口 chunk 及 chunk 中的 module 的渲染模板。
2.2.5 模块构建四队列
webpack 的模块创建、依赖模块的解析创建及编译工作依靠以下四个队列实现。相较于上一代 webpack ,webpack v5 最大的差别在递归的实现,在 v5 中使用异步队列实现的递归解析。而上一代则使用回调组织的一部递归过程。
下面我们先来看看这些队列的作用:
1)this.processDependenciesQueue
这是 webpack 在构建中使用的一个异步队列,实例化自 AsyncQueue:
js
this.processDependenciesQueue = new AsyncQueue({
name: "processDependencies",
parallelism: options.parallelism || 100,
processor: this._processModuleDependencies.bind(this)
});
该队列是 webpack 处理模块创建及依赖解析的四个队列的最顶层队列,也就是说无论后面的 factorizeQueue、addModuleQueue、buildQueue 哪个队列消耗时都会触发 processDependenciesQueue 工作。这一点请你牢记,这是理解这个递归的关键信息!
该队列的 processor 为 this._processModuleDependencies
方法。接收 factorizeQueue 的处理结果,将 factorize 创建的模块加入到队列,随即被 processDependenciesQueue 队里的 processor _processModuleDependencies
消耗,用于处理构建中当前模块的依赖模块的构建。
2) this.addModuleQueue
js
this.addModuleQueue = new AsyncQueue({
name: "addModule",
parent: this.processDependenciesQueue,
getKey: module => module.identifier(),
processor: this._addModule.bind(this)
});
addModuleQueue 的 processor 为 this._addModule()
方法。
该 processor 方法的主要作用是根据 module.identifier 判断并尝试获取缓存,然后将 module 加入到 this.modules 和 this._modules
中。
接着去执行addModuleQueue 时传入的回调函数,建立 originModule.setResolvedModule、moduleGraph.setIssuerIfUnset 及其依赖的关系,也就是 compilation.moduleGraph。
最后将该模块的依赖加入到 this.processDependenciesQueue 中进行依赖解析;
3)this.factorizeQueue
factorizeQueue 工厂队列,其 processor 为 this._factorizeModule
方法,该方法内部调用 factory.create 也就是 NormalModuleFactory.create 方法创建模块实例;
js
this.factorizeQueue = new AsyncQueue({
name: "factorize",
parent: this.addModuleQueue,
processor: this._factorizeModule.bind(this)
});
该 processor 包含模块的创建工作全流程。
4)this.buildQueue
buildQueue 用于处理模块构建的队列,该队列的 processor 是 this._buildModule
方法,该方法内部主要用于调用各个 module.build 方法进行模块的构建工作。
并在完成构建后把构建所得内容加入到 this.processDependenciesQueue 队列中,这样也就完成了整个队列的递归过程;
js
this.buildQueue = new AsyncQueue({
name: "build",
parent: this.factorizeQueue,
processor: this._buildModule.bind(this)
});
三、总结
本文详述了 compilation 对象的创建过程,期间讨论了以下内容:
- Compilation 类型的信息,包括定义文件模块、参数等;
- Compilation 的构造函数的主要逻辑:
- 2.1 compilation.hooks 定义;
- 2.2 compilation.mainTemplate 的作用------生成主入口 chunk 的模板;
- 2.3 compilation.chunkTemplate 的作用------生成非入口 chunk 的模板;
- 2.4 compilation.runteimTemplate 的作用 ------ 生成 webpack 运行时相关代码(runtime)模板;
- 2.5 compilation.moduleTemplate 的作用 ------ 生成 module 相关代码的模板;
- 2.6 模块构建的四队列的作用,并简述了各个队列的 processor 函数的作用,另外大致讲述了这四个队列的协同关系;