Compiler文件分析
之前说到Webpack把构建任务交给了Compiler进行处理,选取Compiler源码进行分析,看看具体做了一些什么,精简过后的源码如下:
"use strict";
// 省略其他引入
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
const Compilation = require("./Compilation");
const Stats = require("./Stats");
const Watching = require("./Watching");
// 省略其他引入
class Compiler extends Tapable {
constructor(context) {
super();
// 定义生命周期钩子函数
this.hooks = {
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
additionalPass: new AsyncSeriesHook([]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
emit: new AsyncSeriesHook(["compilation"]),
assetEmitted: new AsyncSeriesHook(["file", "content"]),
afterEmit: new AsyncSeriesHook(["compilation"]),
thisCompilation: new SyncHook(["compilation", "params"]),
compilation: new SyncHook(["compilation", "params"]),
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
contextModuleFactory: new SyncHook(["contextModulefactory"]),
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
watchRun: new AsyncSeriesHook(["compiler"]),
failed: new SyncHook(["error"]),
invalid: new SyncHook(["filename", "changeTime"]),
watchClose: new SyncHook([]),
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
environment: new SyncHook([]),
afterEnvironment: new SyncHook([]),
afterPlugins: new SyncHook(["compiler"]),
afterResolvers: new SyncHook(["compiler"]),
entryOption: new SyncBailHook(["context", "entry"])
};
this.name = undefined;
this.parentCompilation = undefined;
this.outputPath = "";
this.outputFileSystem = null;
this.inputFileSystem = null;
this.recordsInputPath = null;
this.recordsOutputPath = null;
this.records = {};
this.removedFiles = new Set();
this.fileTimestamps = new Map();
this.contextTimestamps = new Map();
this.resolverFactory = new ResolverFactory();
this.infrastructureLogger = undefined;
this.options = /** @type {webpackOptions} */ ({});
this.context = context;
this.requestShortener = new RequestShortener(context);
this.running = false;
this.watchMode = false;
this._assetEmittingSourceCache = new WeakMap();
this._assetEmittingWrittenFiles = new Map();
}
// 文件监听
watch(watchOptions, handler) { }
// 开始执行一次新的编译
run(callback) {
// 此次编译完成
const onCompiled = (err, compilation) => {
// 输出资源
this.emitAssets(compilation, err => {
// 输出编译记录
this.emitRecords(err => { });
});
};
// 发布开始编译之前的信息,订阅该钩子函数的此时可以做一些事情,比如loader,plugin......
this.hooks.beforeRun.callAsync(this, err => { });
}
// 输出资源
emitAssets(compilation, callback) {
// 发布输出资源的信息,订阅该钩子函数的此时可以做一些事情,比如loader,plugin......
this.hooks.emit.callAsync(compilation, err => { });
}
// 输出编译记录
emitRecords(callback) { }
// 读取记录
readRecords(callback) { }
createCompilation() {
return new Compilation(this);
}
// 获取compilation实例
newCompilation(params) { }
// 编译
compile(callback) {
// 发布开始编译之前的信息,订阅该钩子函数的此时可以做一些事情
this.hooks.beforeCompile.callAsync(params, err => { });
}
}
module.exports = Compiler;
可以看出Compiler类的工作流程总的可以概括为以下三个阶段:
- 初始化
- 编译
- 输出
这三个阶段是一次执行所要依次经历的阶段,如果开启了监听模式,首先还是会执行一遍上面三个阶段,监听到文件发生变化以后,会再次依次执行编译阶段和输出阶段。
因为Compiler集成自Tapable类,所以在不同阶段触发不同的生命周期钩子函数,也就是发生事件,下面详细介绍一下各个阶段发生的事件。
初始化阶段
编译阶段
Compilation是编译阶段中最重要的事件,因为Compilation阶段调用了Loader去转换模块,Compilation阶段也会发生很多事件。
输出阶段
输出阶段得到了各个模块经过转换后的结果和其依赖关系,并且将相关模块组合成一个个Chunk,然后根据Chunk的类型,使用对应的模版生成最终要输出的文件内容。
通过上面三个阶段的分析,可以总结出Webpack从启动到结束会经历以下流程:
- 初始化参数:根据配置文件和Shell语句中从读取合并参数,得到最终的参数。
- 开始编译:通过参数初始化Compiler对象,加载所有配置的插件,调用compiler的run/watch方法开始执行编译。
- 确定入口:根据配置中的entry确定入口。
- 编译模块:从入口文件开始,调用所配置的Loader对模块进行解析转换,再递归找出模块依赖的模块,重复执行本步骤,直到所有入口依赖的模块都得到本步骤处理。
- 完成模块编译:根据上一步的操作,得到所有模块被转换后的内容以及它们之间的依赖关系。
- 输出资源:根据入口和模块之间的依赖关系,组成一个个包含多个模块的Chunk,再将每个Chunk转换成一个单独的文件加入到输出列表,这是修改输出内容最后的机会。
- 输出完成:确定好输出内容后,根据配置的输出文件名称和路径,将文件的内容写入到系统中。
了解Webpack的工作流程,是进阶Wbepack必经的步骤,在遇到构建问题和想要配置功能更加丰富的时候,才能从根本上去解决问题和输出工作方案。
本章节着重讲了Compiler和Comilation,是因为它们负责Webpack的主要功能,除了上面介绍的以后,还有很多对象在Webpack中扮演者重要的角色,比如负责模块路径处理的Resolver和解析模块的Parser(参考7.4,7.5小节)。这些对象都继承自Tapable,可以发布许多事件,构成庞大的发布/订阅的系统,为插件的扩展提供可能,极大地丰富了Webpack的可能性。