前言#
前面的文章分析了webpack-cli和webpack()执行的流程,上篇还留了两个点没说new compiler和compiler.run 做了什么。
如果你还没看过之前的文章,可以先去看下。
进入webpack/cli/webpack.js文件里#
找到createCompiler方法
/**
* @param {WebpackOptions} rawOptions options object
* @returns {Compiler} a 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;
}; 找到关键点:new Compiler(options.context, options)
进入webpack/lib/Compiler.js文件内#
直接看constructor
const webpack = require("./");
constructor(context, options = ({})) {
this.hooks = Object.freeze({
initialize: new SyncHook([]),
done: new AsyncSeriesHook(["stats"]),
...省略,都是生命周期钩子方法,不知道可以去官网看
})
this.webpack = webpack;
this.watching = undefined;
this.outputFileSystem = null;
this.intermediateFileSystem = null;
this.inputFileSystem = null;
this.watchFileSystem = null;
...其它省略
}可以看到非常简单,只是初始化数据
new SyncHook一个同步hook实例,这里先跳过,感兴趣的大佬可以看下tapable:
GitHub - webpack/tapable: Just a little module for plugins.github.com/webpack/tapable
this.webpack = webpack就相当于前面将一堆plugin等放到webpack/lib/index.js的导出function上this.watching和dev相关就不多说,我们说的是build的- 剩下的
outputFileSystem等都是上篇文章里提到过的**nodeEnvironmentPlugin**里做的事情,就不多说了。
简单的说,创建compiler实例的过程中初始化一些状态。
然后我们来看下compiler.run做了什么事。
compiler.run#
run(callback) {
...省略一堆log相关
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);
});
});
});
};
if (this.idle) {
this.cache.endIdle(err => {
if (err) return finalCallback(err);
this.idle = false;
run();
});
} else {
run();
}
}beforeRun生命周期hook,不多说run同上this.compile执行编译方法,onCompiled先不分析,等分析完compile方法后再分析
compile#
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
if (err) return callback(err);
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});
}照例少了logger相关的代码
this.newCompilationParams()获取编译所需要的参数数据
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory()
};
return params;
} 这里就不分析this.createNormalModuleFactory和this.createContextModuleFactory的代码了,只说下做了什么,后面有时间就分析下毕竟他俩还是挺重要的。
先来看下数据

可以看到这两个都有hooks,实际上他俩的hooks也都是基于tapable的hooks实现的。
至于做了什么,来看下官方原话

Normal Module Factory Hooks

Context Module Factory Hooks
然后我们再来看下肖恩大佬的解释

肖恩大佬的解释
结合两者:
NormalModuleFactory Hooks: 简单的说就是它将resolver、loaders、以及创建NormalModule的实例绑定到一起,compiler使用它将一个个存在于文件中的request转换为模块实例,最终所有的依赖都被处理成模块实例。ContextModuleFactory Hooks: 结合两者的说明以及以下的require.context,可以理解为它和NormalModuleFactory目的一样,但它是用于特殊模块类型ContextModules(允许动态导入)。compiler使用它从webpack特有的require.contextAPI生成依赖项,它会根据请求的目录为每一个文件生成请求项,通过不同的正则规则,过滤掉不需要处理的(exclude),匹配到需要处理的文件并传给NormalModuleFactory。

他们在create之后会分别调用同名的hooks:normalModuleFactor、contextModuleFactory
说的可能依旧不清不楚的,如果大佬有需要可以看注释里的链接。
回到compile方法里,我们接着看。
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
if (err) return callback(err);
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});- 在获取完编译需要的
params之后会触发beforeCompile的hooks,并且在通知完订阅者最后会执行回调 this.hooks.compile.call(params)不多说this.newCompilation(params):创建一个编译实例,就不分析代码了。老样子,看下数据先


呃,太长,总之一个compilation实例对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息,代表了一次资源的构建。
在这期间执行了两个hooks:beforeCompilation和compilation
回到compile方法里
this.hooks.make.callAsync(compilation, err => {
this.hooks.finishMake.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
if (err) return callback(err);
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});this.hooks.make.callAsync异步通知订阅者执行回调,最后再执行这里传入的回调this.hooks.finishMake.callAsync此时所有的模块都已经编译完成并且存放到compilation.modules里 ,是一个嵌套的数组,毕竟一个模块的依赖可能又依赖其它模块process.nextTick放在下一次事件循环中是为了确保异步顺序正确。compilation.finish:完成编译,至于这里面做了什么,简单的说就是触发了自己finish的钩子,然后收集问题compilation.seal:封存,这里面做了一堆事情,简单的说就是将modules转换为chunk,中间做了一堆优化 ,生成的代码最终存放在compilation.assets里 ,看着是不是很熟悉?至于中间的优化就不是这文章要说的内容了,毕竟这几篇文章只是跟着调试来解读执行顺序。

afterCompile不多说
然后回到compile.run方法里,看下callback:onCompiled
onCompiled
const onCompiled = (err, compilation) => {
...省略
if (this.hooks.shouldEmit.call(compilation) === false) {
...省略
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
return finalCallback(null, stats);
});
return;
}
process.nextTick(() => {
this.emitAssets(compilation, err => {
...省略
if (compilation.hooks.needAdditionalPass.call()) {
...省略
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
this.hooks.additionalPass.callAsync(err => {
this.compile(onCompiled);
});
});
return;
}
this.emitRecords(err => {
...省略
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
...省略
this.cache.storeBuildDependencies(compilation.buildDependencies,
err => {
return finalCallback(null, stats);
});
});
});
});
})
}hooks.shouldEmit在output之前触发,需要返回布尔值提示是否需要emitnew Status:status实例对象,看下数据

hooks.done编译完成finalCallback:没什么好说的,里面的Idle就不说了,不影响流程。
const finalCallback = (err, stats) => {
if (logger) logger.time("beginIdle");
this.idle = true;
this.cache.beginIdle();
this.idle = true;
if (logger) logger.timeEnd("beginIdle");
this.running = false;
if (err) {
this.hooks.failed.call(err);
}
if (callback !== undefined) callback(err, stats);
this.hooks.afterDone.call(stats);
}; this.emitAssets:看名字就知道这个方法会把打包后的源码输出到output中,就不分析代码了,有空再分析,在执行完会触发afterEmit、assetsEmitted的hooks。compilation.hooks.needAdditionalPass.call()判断是否有assets需要在emit之后再加工 ,如果需要,会则执行完needAditionalPass之后再次编译。this.emitRecords看名字就知道是输出构建的记录,就不多说了。this.cache.storeBuildDependencies这个也是,存储构建好了的依赖项。
以上就是这次的分析,因为篇幅等问题,对于一些比较重要的东西都没有进行代码分析,看着有些空洞。。。。
参考#
- compiler Hooks https://webpack.js.org/api/compiler-hooks/#beforerun
- hooks https://webpack.js.org/api/contextmodulefactory-hooks
- the-contributors-guide-to-webpack-part-2 https://medium.com/webpack/the-contributors-guide-to-webpack-part-2-9fd5e658e08c
- compilation.finish https://webpack.docschina.org/api/compilation-object/#finish
- shouldEmit https://webpack.js.org/api/compiler-hooks/#shouldemit
- hooks-done https://webpack.js.org/api/compiler-hooks/#done
- afterEmit and assetEmitted https://webpack.js.org/api/compiler-hooks/#assetemitted
- compilation-needAditionalPass https://webpack.js.org/api/compilation-hooks/#needadditionalpass
发布于 2022-08-30 17:52
