最近很多人提问webpack中的钩子机制底层是怎样的,本文从模块层面出发,分析一下hooks钩子的传递,以及hook这个底层数据结构的实现。
源码如下
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook} */
failed: new SyncHook(["error"]),
/** @type {SyncHook} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook} */
entryOption: new SyncBailHook(["context", "entry"])
};
}
}
源码如下,从源码可以看出compilation在初始化的时候传入了compiler。
class Compilation extends Tapable {
/**
* Creates an instance of Compilation.
* @param {Compiler} compiler the compiler which created the compilation
*/
constructor(compiler) {
super();
this.hooks = {
/** @type {SyncHook} */
buildModule: new SyncHook(["module"]),
/** @type {SyncHook} */
rebuildModule: new SyncHook(["module"]),
/** @type {SyncHook} */
failedModule: new SyncHook(["module", "error"]),
/** @type {SyncHook} */
succeedModule: new SyncHook(["module"]),
/** @type {SyncHook} */
addEntry: new SyncHook(["entry", "name"]),
/** @type {SyncHook} */
failedEntry: new SyncHook(["entry", "name", "error"]),
/** @type {SyncHook} */
succeedEntry: new SyncHook(["entry", "name", "module"]),
/** @type {SyncWaterfallHook} */
dependencyReference: new SyncWaterfallHook([
"dependencyReference",
"dependency",
"module"
]),
/** @type {AsyncSeriesHook} */
finishModules: new AsyncSeriesHook(["modules"]),
/** @type {SyncHook} */
finishRebuildingModule: new SyncHook(["module"]),
/** @type {SyncHook} */
unseal: new SyncHook([]),
/** @type {SyncHook} */
seal: new SyncHook([]),
/** @type {SyncHook} */
beforeChunks: new SyncHook([]),
/** @type {SyncHook} */
afterChunks: new SyncHook(["chunks"]),
/** @type {SyncBailHook} */
optimizeDependenciesBasic: new SyncBailHook(["modules"]),
/** @type {SyncBailHook} */
optimizeDependencies: new SyncBailHook(["modules"]),
/** @type {SyncBailHook} */
optimizeDependenciesAdvanced: new SyncBailHook(["modules"]),
/** @type {SyncBailHook} */
afterOptimizeDependencies: new SyncHook(["modules"]),
/** @type {SyncHook} */
optimize: new SyncHook([]),
/** @type {SyncBailHook} */
optimizeModulesBasic: new SyncBailHook(["modules"]),
/** @type {SyncBailHook} */
optimizeModules: new SyncBailHook(["modules"]),
/** @type {SyncBailHook} */
optimizeModulesAdvanced: new SyncBailHook(["modules"]),
/** @type {SyncHook} */
afterOptimizeModules: new SyncHook(["modules"]),
/** @type {SyncBailHook} */
optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncBailHook} */
optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncBailHook} */
optimizeChunksAdvanced: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncHook} */
afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]),
/** @type {AsyncSeriesHook} */
optimizeTree: new AsyncSeriesHook(["chunks", "modules"]),
/** @type {SyncHook} */
afterOptimizeTree: new SyncHook(["chunks", "modules"]),
/** @type {SyncBailHook} */
optimizeChunkModulesBasic: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncBailHook} */
optimizeChunkModules: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncBailHook} */
optimizeChunkModulesAdvanced: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncHook} */
afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]),
/** @type {SyncBailHook} */
shouldRecord: new SyncBailHook([]),
/** @type {SyncHook} */
reviveModules: new SyncHook(["modules", "records"]),
/** @type {SyncHook} */
optimizeModuleOrder: new SyncHook(["modules"]),
/** @type {SyncHook} */
advancedOptimizeModuleOrder: new SyncHook(["modules"]),
/** @type {SyncHook} */
beforeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook} */
moduleIds: new SyncHook(["modules"]),
/** @type {SyncHook} */
optimizeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook} */
afterOptimizeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook} */
reviveChunks: new SyncHook(["chunks", "records"]),
/** @type {SyncHook} */
optimizeChunkOrder: new SyncHook(["chunks"]),
/** @type {SyncHook} */
beforeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook} */
optimizeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook} */
afterOptimizeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook} */
recordModules: new SyncHook(["modules", "records"]),
/** @type {SyncHook} */
recordChunks: new SyncHook(["chunks", "records"]),
/** @type {SyncHook} */
beforeHash: new SyncHook([]),
/** @type {SyncHook} */
contentHash: new SyncHook(["chunk"]),
/** @type {SyncHook} */
afterHash: new SyncHook([]),
/** @type {SyncHook} */
recordHash: new SyncHook(["records"]),
/** @type {SyncHook} */
record: new SyncHook(["compilation", "records"]),
/** @type {SyncHook} */
beforeModuleAssets: new SyncHook([]),
/** @type {SyncBailHook} */
shouldGenerateChunkAssets: new SyncBailHook([]),
/** @type {SyncHook} */
beforeChunkAssets: new SyncHook([]),
/** @type {SyncHook} */
additionalChunkAssets: new SyncHook(["chunks"]),
/** @type {AsyncSeriesHook} */
additionalAssets: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook} */
optimizeChunkAssets: new AsyncSeriesHook(["chunks"]),
/** @type {SyncHook} */
afterOptimizeChunkAssets: new SyncHook(["chunks"]),
/** @type {AsyncSeriesHook} */
optimizeAssets: new AsyncSeriesHook(["assets"]),
/** @type {SyncHook} */
afterOptimizeAssets: new SyncHook(["assets"]),
/** @type {SyncBailHook} */
needAdditionalSeal: new SyncBailHook([]),
/** @type {AsyncSeriesHook} */
afterSeal: new AsyncSeriesHook([]),
/** @type {SyncHook} */
chunkHash: new SyncHook(["chunk", "chunkHash"]),
/** @type {SyncHook} */
moduleAsset: new SyncHook(["module", "filename"]),
/** @type {SyncHook} */
chunkAsset: new SyncHook(["chunk", "filename"]),
/** @type {SyncWaterfallHook} */
assetPath: new SyncWaterfallHook(["filename", "data"]), // TODO MainTemplate
/** @type {SyncBailHook} */
needAdditionalPass: new SyncBailHook([]),
/** @type {SyncHook} */
childCompiler: new SyncHook([
"childCompiler",
"compilerName",
"compilerIndex"
]),
/** @type {SyncBailHook} */
log: new SyncBailHook(["origin", "logEntry"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook
class NormalModuleFactory extends Tapable {
constructor(context, resolverFactory, options) {
super();
this.hooks = {
resolver: new SyncWaterfallHook(["resolver"]),
factory: new SyncWaterfallHook(["factory"]),
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
createModule: new SyncBailHook(["data"]),
module: new SyncWaterfallHook(["module", "data"]),
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
createGenerator: new HookMap(
() => new SyncBailHook(["generatorOptions"])
),
generator: new HookMap(
() => new SyncHook(["generator", "generatorOptions"])
)
};
}
}
class ContextModuleFactory extends Tapable {
constructor(resolverFactory) {
super();
this.hooks = {
/** @type {AsyncSeriesWaterfallHook} */
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
/** @type {AsyncSeriesWaterfallHook} */
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
/** @type {SyncWaterfallHook} */
contextModuleFiles: new SyncWaterfallHook(["files"]),
/** @type {SyncWaterfallHook} */
alternatives: new AsyncSeriesWaterfallHook(["modules"])
};
this._pluginCompat.tap("ContextModuleFactory", options => {
switch (options.name) {
case "before-resolve":
case "after-resolve":
case "alternatives":
options.async = true;
break;
}
});
this.resolverFactory = resolverFactory;
}
}
class ResolverFactory extends Tapable {
constructor() {
super();
this.hooks = {
resolveOptions: new HookMap(
() => new SyncWaterfallHook(["resolveOptions"])
),
resolver: new HookMap(() => new SyncHook(["resolver", "resolveOptions"]))
};
}
compiler, compilation, NormalModuleFactory均继承自tapable模块,tapable模块提供了很多钩子函数的基类。如图:
说明:
这里面hooks,HookCodeFactory是其他钩子函数的基类,后面在webpack中订阅的事件最终都会放入taps栈中,订阅分为三种类型async, sync, promise,调用时会通过_createCall方法调用HookCodeFactory的create方法创建委托函数。
从源码中还可以看出,所有的hooks钩子都有两个类,一个是钩子,一个是钩子工厂。
钩子继承自hook类,钩子工厂继承自HookCodeFactory。
HookMap,MultiHook也是两个独立的类,用来操作hook类