最近很多人提问webpack中的钩子机制底层是怎样的,本文从模块层面出发,分析一下hooks钩子的传递,以及hook这个底层数据结构的实现。
compiler中的hooks:
源码如下
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
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<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
}
}
compilation中的hooks:
源码如下,从源码可以看出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<Module>} */
buildModule: new SyncHook(["module"]),
/** @type {SyncHook<Module>} */
rebuildModule: new SyncHook(["module"]),
/** @type {SyncHook<Module, Error>} */
failedModule: new SyncHook(["module", "error"]),
/** @type {SyncHook<Module>} */
succeedModule: new SyncHook(["module"]),
/** @type {SyncHook<Dependency, string>} */
addEntry: new SyncHook(["entry", "name"]),
/** @type {SyncHook<Dependency, string, Error>} */
failedEntry: new SyncHook(["entry", "name", "error"]),
/** @type {SyncHook<Dependency, string, Module>} */
succeedEntry: new SyncHook(["entry", "name", "module"]),
/** @type {SyncWaterfallHook<DependencyReference, Dependency, Module>} */
dependencyReference: new SyncWaterfallHook([
"dependencyReference",
"dependency",
"module"
]),
/** @type {AsyncSeriesHook<Module[]>} */
finishModules: new AsyncSeriesHook(["modules"]),
/** @type {SyncHook<Module>} */
finishRebuildingModule: new SyncHook(["module"]),
/** @type {SyncHook} */
unseal: new SyncHook([]),
/** @type {SyncHook} */
seal: new SyncHook([]),
/** @type {SyncHook} */
beforeChunks: new SyncHook([]),
/** @type {SyncHook<Chunk[]>} */
afterChunks: new SyncHook(["chunks"]),
/** @type {SyncBailHook<Module[]>} */
optimizeDependenciesBasic: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeDependencies: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeDependenciesAdvanced: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
afterOptimizeDependencies: new SyncHook(["modules"]),
/** @type {SyncHook} */
optimize: new SyncHook([]),
/** @type {SyncBailHook<Module[]>} */
optimizeModulesBasic: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeModules: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeModulesAdvanced: new SyncBailHook(["modules"]),
/** @type {SyncHook<Module[]>} */
afterOptimizeModules: new SyncHook(["modules"]),
/** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */
optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */
optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */
optimizeChunksAdvanced: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncHook<Chunk[], ChunkGroup[]>} */
afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]),
/** @type {AsyncSeriesHook<Chunk[], Module[]>} */
optimizeTree: new AsyncSeriesHook(["chunks", "modules"]),
/** @type {SyncHook<Chunk[], Module[]>} */
afterOptimizeTree: new SyncHook(["chunks", "modules"]),
/** @type {SyncBailHook<Chunk[], Module[]>} */
optimizeChunkModulesBasic: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncBailHook<Chunk[], Module[]>} */
optimizeChunkModules: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncBailHook<Chunk[], Module[]>} */
optimizeChunkModulesAdvanced: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncHook<Chunk[], Module[]>} */
afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]),
/** @type {SyncBailHook} */
shouldRecord: new SyncBailHook([]),
/** @type {SyncHook<Module[], any>} */
reviveModules: new SyncHook(["modules", "records"]),
/** @type {SyncHook<Module[]>} */
optimizeModuleOrder: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
advancedOptimizeModuleOrder: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
beforeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
moduleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
optimizeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
afterOptimizeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Chunk[], any>} */
reviveChunks: new SyncHook(["chunks", "records"]),
/** @type {SyncHook<Chunk[]>} */
optimizeChunkOrder: new SyncHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
beforeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
optimizeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
afterOptimizeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook<Module[], any>} */
recordModules: new SyncHook(["modules", "records"]),
/** @type {SyncHook<Chunk[], any>} */
recordChunks: new SyncHook(["chunks", "records"]),
/** @type {SyncHook} */
beforeHash: new SyncHook([]),
/** @type {SyncHook<Chunk>} */
contentHash: new SyncHook(["chunk"]),
/** @type {SyncHook} */
afterHash: new SyncHook([]),
/** @type {SyncHook<any>} */
recordHash: new SyncHook(["records"]),
/** @type {SyncHook<Compilation, any>} */
record: new SyncHook(["compilation", "records"]),
/** @type {SyncHook} */
beforeModuleAssets: new SyncHook([]),
/** @type {SyncBailHook} */
shouldGenerateChunkAssets: new SyncBailHook([]),
/** @type {SyncHook} */
beforeChunkAssets: new SyncHook([]),
/** @type {SyncHook<Chunk[]>} */
additionalChunkAssets: new SyncHook(["chunks"]),
/** @type {AsyncSeriesHook} */
additionalAssets: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Chunk[]>} */
optimizeChunkAssets: new AsyncSeriesHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
afterOptimizeChunkAssets: new SyncHook(["chunks"]),
/** @type {AsyncSeriesHook<CompilationAssets>} */
optimizeAssets: new AsyncSeriesHook(["assets"]),
/** @type {SyncHook<CompilationAssets>} */
afterOptimizeAssets: new SyncHook(["assets"]),
/** @type {SyncBailHook} */
needAdditionalSeal: new SyncBailHook([]),
/** @type {AsyncSeriesHook} */
afterSeal: new AsyncSeriesHook([]),
/** @type {SyncHook<Chunk, Hash>} */
chunkHash: new SyncHook(["chunk", "chunkHash"]),
/** @type {SyncHook<Module, string>} */
moduleAsset: new SyncHook(["module", "filename"]),
/** @type {SyncHook<Chunk, string>} */
chunkAsset: new SyncHook(["chunk", "filename"]),
/** @type {SyncWaterfallHook<string, TODO>} */
assetPath: new SyncWaterfallHook(["filename", "data"]), // TODO MainTemplate
/** @type {SyncBailHook} */
needAdditionalPass: new SyncBailHook([]),
/** @type {SyncHook<Compiler, string, number>} */
childCompiler: new SyncHook([
"childCompiler",
"compilerName",
"compilerIndex"
]),
/** @type {SyncBailHook<string, LogEntry>} */
log: new SyncBailHook(["origin", "logEntry"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook<object, Module>} */
normalModuleLoader: new SyncHook(["loaderContext", "module"]),
/** @type {SyncBailHook<Chunk[]>} */
optimizeExtractedChunksBasic: new SyncBailHook(["chunks"]),
/** @type {SyncBailHook<Chunk[]>} */
optimizeExtractedChunks: new SyncBailHook(["chunks"]),
/** @type {SyncBailHook<Chunk[]>} */
optimizeExtractedChunksAdvanced: new SyncBailHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
afterOptimizeExtractedChunks: new SyncHook(["chunks"])
};
}
}
NormalModuleFactory中的hooks:源码如下
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"])
)
};
}
}
ContextModuleFactory
class ContextModuleFactory extends Tapable {
constructor(resolverFactory) {
super();
this.hooks = {
/** @type {AsyncSeriesWaterfallHook<TODO>} */
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
/** @type {AsyncSeriesWaterfallHook<TODO>} */
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
/** @type {SyncWaterfallHook<string[]>} */
contextModuleFiles: new SyncWaterfallHook(["files"]),
/** @type {SyncWaterfallHook<TODO[]>} */
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;
}
}
ResolverFactoy
class ResolverFactory extends Tapable {
constructor() {
super();
this.hooks = {
resolveOptions: new HookMap(
() => new SyncWaterfallHook(["resolveOptions"])
),
resolver: new HookMap(() => new SyncHook(["resolver", "resolveOptions"]))
};
}
tapable实现结构原理:
compiler, compilation, NormalModuleFactory均继承自tapable模块,tapable模块提供了很多钩子函数的基类。如图:
说明:
这里面hooks,HookCodeFactory是其他钩子函数的基类,后面在webpack中订阅的事件最终都会放入taps栈中,订阅分为三种类型async, sync, promise,调用时会通过_createCall方法调用HookCodeFactory的create方法创建委托函数。
从源码中还可以看出,所有的hooks钩子都有两个类,一个是钩子,一个是钩子工厂。
钩子继承自hook类,钩子工厂继承自HookCodeFactory。
HookMap,MultiHook也是两个独立的类,用来操作hook类
关于tapable的实现原理详解参见基于数据结构从源码解析webpack核心模块tapable