tapable
tapable 导出了 9 个 hooks
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
- AsyncParallelHook
- AsyncParallelBailHook
- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
上述 9 个 hooks 都继承自 Hook 这个 class
tapable Hook 解析
hook 对外提供了
其中
isUsed 源码
1
2
3
4
5 isUsed() {
return this.taps.length > 0 || this.interceptors.length > 0;
}
1 2 3 4 5 6 7 | export interface Tap { name: string; // 事件名称,一般就是 plugin 的名字 type: TapType; // 支持三种类型 'sync' 'async' 'promise' fn: Function; stage: number; context: boolean; } |
1 2 3 4 5 | const sync = new SyncHook(['arg1', 'arg2']) // 'arg1' 'arg2' 为参数占位符 sync.tap('Test', (arg1, arg2) => { console.log(arg1, arg2) // a2 }) sync.call('a', '2') |
其中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | const TAP_ASYNC = () => { throw new Error("tapAsync is not supported on a SyncHook"); }; const TAP_PROMISE = () => { throw new Error("tapPromise is not supported on a SyncHook"); }; const COMPILE = function(options) { factory.setup(this, options); return factory.create(options); }; function SyncHook(args = [], name = undefined) { const hook = new Hook(args, name); hook.constructor = SyncHook; hook.tapAsync = TAP_ASYNC; hook.tapPromise = TAP_PROMISE; hook.compile = COMPILE; return hook; } SyncHook.prototype = null; |
在这里面我们可以看到
一个简单的使用示范
下面的例子会给大家带来一个简单地示范
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class TapableTest { constructor() { this.hooks = { sync: new SyncHook(['context', 'hi']), syncBail: new SyncBailHook(), syncLoop: new SyncLoopHook(), syncWaterfall: new SyncWaterfallHook(['syncwaterfall']), asyncParallel: new AsyncParallelHook(), asyncParallelBail: new AsyncParallelBailHook(), asyncSeries: new AsyncSeriesHook(), asyncSeriesBail: new AsyncSeriesBailHook(), asyncSeriesWaterfall: new AsyncSeriesWaterfallHook(['asyncwaterfall']) } } emitSync() { this.hooks.sync.call(this, err => { console.log(this.hooks.sync.promise) console.log(err) }) } emitAyncSeries() { this.hooks.asyncSeries.callAsync(err => { if (err) console.log(err) }) } } const test = new TapableTest() test.hooks.sync.tap('TestPlugin', (context, callback) => { console.log('trigger: ', context) callback(new Error('this is sync error')) }) test.hooks.asyncSeries.tapAsync('AsyncSeriesPlugin', callback => { callback(new Error('this is async series error')) }) test.emitSync() test.emitAyncSeries() |
上述的运行结果可以这查看 runkit
下面来聊一聊 webpack 中的插件是如何依赖 tapable 的
webpack 插件被注入的时机
当我们定义了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | const createCompiler = rawOptions => { const options = getNormalizedWebpackOptions(rawOptions); applyWebpackOptionsBaseDefaults(options); const compiler = new Compiler(options.context); compiler.options = 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; }; |
我们可以看到遍历
- 我们的 plugin 可以以函数的方式被
webpack 调用,也就是说我们可以用函数来写插件,这个函数的作用域是当前的compiler ,函数也会接收到一个compiler - 可以传入一个包含
apply 方法的对象实例,apply 方法会被传入compiler
所以这也就解释了为什么我们的插件需要
进入 Compiler 一探究竟
上一个中我们了解到了
进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | this.hooks = Object.freeze({ /** @type {SyncHook<[]>} */ initialize: new SyncHook([]), /** @type {SyncBailHook<[Compilation], boolean>} */ shouldEmit: new SyncBailHook(["compilation"]), /** @type {AsyncSeriesHook<[Stats]>} */ done: new AsyncSeriesHook(["stats"]), /** @type {SyncHook<[Stats]>} */ afterDone: new SyncHook(["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, AssetEmittedInfo]>} */ assetEmitted: new AsyncSeriesHook(["file", "info"]), /** @type {AsyncSeriesHook<[Compilation]>} */ afterEmit: new AsyncSeriesHook(["compilation"]) ... }) |
看到这些
比如我们经常用到的
1 2 3 | compiler.hooks.emit.tapAsync('HtmlWebpackPlugin', (compiler, callback) => { ... }) |
说明
通过深入了解,
上述内容如有错误,请指正