Before
上一节webpack5源码之旅 - 先导介绍了一些编译过程会遇到的概念,例如compiler、compilation、plugin等等, 那么这一节我们就从compiler开始看看这些词到底扮演了什么样的角色。
创建Compiler
(options, callback) => {
const create = () => {
let compiler;
if (Array.isArray(options)) {
} else {
const webpackOptions = /** @type {WebpackOptions} */ (options);
/** @type {Compiler} */
compiler = createCompiler(webpackOptions);
}
return { compiler, watch, watchOptions };
};
if (callback) {
try {
const { compiler, watch, watchOptions } = create();
...
}
}
上回说到require('webpack')最终得到的是(options, callback) => 这个方法,那么现在我们就来执行这个方法(上述代码只保留了主线部分)。
首先就是传入options参数去创建Compiler:
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context, options);
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
传入的rawOptions就是经过cli处理后的参数,首先通过getNormalizedWebpackOptions以及applyWebpackOptionsBaseDefaults方法,给没有设置的配置属性加上默认值, 处理后的options会多出来很多很多的属性,这些属性都是可以在配置文件里面配置的。
this.hooks = Object.freeze({
/** @type {SyncHook<[]>} */
initialize: new SyncHook([]),
/** @type {SyncBailHook<[Compilation], boolean>} */
shouldEmit: new SyncBailHook(["compilation"]),
...
})
接着就是创建compiler,这边我们先简单看一下Compiler的构造函数,里面有一个this.hooks对象,初始化了各种钩子,这些钩子会在不同生命周期被触发。
有了compiler对象就可以往上面挂载插件了。
首先挂载了NodeEnvironmentPlugin,然后遍历配置中的plugins,执行每个plugin的apply方法,这些apply方法都是往compiler.hooks.**上挂载,等钩子被触发时,就去实现对应功能。
接下来的applyWebpackOptionsDefaults方法还是给配置加默认值的。
然后执行environment和afterEnvironment两个钩子,这时所有监听了这两个钩子的插件都会触发对应的事件。
WebpackOptionsApply的process方法是根据options中的配置去注册对应的插件(webpack内置的插件)。
最后执行initialize钩子。
创建Compilation
createCompiler结束后,执行compiler.run():
run(callback) {
...
const run = () => {
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
};
...
run()
}
在run方法中,首先执行beforeRun钩子,前面createCompiler中注册的NodeEnvironmentPlugin就监听了这个钩子,这时就会走进对应的回调方法中。
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
if (compiler.inputFileSystem === inputFileSystem) {
compiler.fsStartTime = Date.now();
inputFileSystem.purge();
}
});
等所有注册事件执行完就会进入beforeRun.callAsync的第二个参数callback方法中,执行run钩子, 最后执行this.compile(它传入的回调函数onCompiled会在最终的seal阶段执行):
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
...
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
...
this.hooks.make.callAsync(compilation, err => {
...
});
});
}
compile方法首先生成params,这是后面创建Compilation所需的参数。
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
params中有两个属性normalModuleFactory和contextModuleFactory,一个用以生成normalModule,另一个用以生成contextModule。
一般的模块都是normalModule,contextModule是针对通过require.context引入的模块的。
执行beforeCompile钩子,接着传入params执行createCompilation方法。
createCompilation(params) {
this._cleanupLastCompilation();
return (this._lastCompilation = new Compilation(this, params));
}
newCompilation(params) {
const compilation = this.createCompilation(params);
compilation.name = this.name;
compilation.records = this.records;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
首先执行了thisCompilation和compilation两个钩子,监听这两个钩子的插件非常之多,都是内置插件,用以实现各种功能。
接着就是执行new Compilation,我们也先去简单看一下Compilation的构造函数,同Compiler相似,它也有this.hooks对象,里面初始化了很多钩子。
还有几个AsyncQueue(webpack5新增内容),这用在后面的构建阶段,我们先简单看一下,留个印象。
AsyncQueue
创建了5个AsyncQueue,分别是factorizeQueue、addModuleQueue、buildQueue、processDependenciesQueue以及rebuildQueue,分别用于
解析模块生成对应Module,添加module到ModuleGraph中,编译模块得到模块的依赖,处理模块内的依赖以及重构建。
其中前4个会在构建阶段递归执行,直到处理完所有的依赖。
创建好Compilation,一切准备工作都完成了,接下去就是执行make钩子,而make钩子也标志着编译正式进入第二阶段 -- 构建,从entry开始寻找依赖构建模块。
钩子
最后总结一下初始化阶段执行的钩子
hook挂载对象 | hook | hook执行文件 | 注册该hook的插件(举例) | 钩子类型(默认SyncHook) |
---|---|---|---|---|
compiler | environment | webpack.js | ||
compiler | afterEnvironment | webpack.js | WatchIgnorePlugin | |
compiler | entryOption | WebpackOptionsApply.js | EntryOptionPlugin | SyncBailHook |
compiler | afterPlugins | WebpackOptionsApply.js | ModuleFederationPlugin | |
compiler | afterResolvers | WebpackOptionsApply.js | ||
compiler | initialize | webpack.js | ||
compiler | beforeRun | Compiler.js | NodeEnvironmentPlugin | AsyncSeriesHook |
compiler | run | Compiler.js | AsyncSeriesHook | |
compiler | normalModuleFactory | Compiler.js | IgnorePlugin | |
compiler | contextModuleFactory | Compiler.js | IgnorePlugin | |
创建normalModuleFactory和contextModuleFactory | ||||
compiler | beforeCompile | Compiler.js | DllReferencePlugin | AsyncSeriesHook |
compiler | compile | Compiler.js | DllReferencePlugin | |
创建Compilation | ||||
compiler | thisCompilation | Compiler.js | ... | |
compiler | compilation | Compiler.js | ...EntryPlugin | |
compiler | make | Compiler.js | EntryPlugin | AsyncParallelHook |