想了解webpack的插件机制,了解Tapable 就很有必要。Tapable 的官方解释就是:一个暴露了很多Hooks,用来插件调用的包。
Tapable
const {
SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook,
AsyncParallelHook, AsyncParallelBailHook,
AsyncSeriesHoos, AsyncSeriesBailHook, AsyncSeriesWaterfallHook,
HookMap, MultiHook
} = require('tapable')
-
分类
Hook分为4个同步钩子和5个异步钩子HookMap和MultiHook都是Tapable导出的辅助类
-
Hook 生成方式
Hook接收一个可选的Array<string>作为参数来构造 -
示例
const synchook = new SyncHook() const synchook1 = new SyncHook(['arrg1', 'arg2]) ... -
最佳实践
最好在一个类中用
hooks属性去初始化所有的钩子:class Car { constructor(args) { this.hooks = { startHook: new SyncHook(), syncBailHook: new SyncBailHook(), syncWaterFallHook: new SyncWaterfallHook(['arg1', 'arg2']), syncLoopHook: new SyncLoopHook(), asyncParallelHook: new AsyncParallelHook(['arg1', 'arg2']), asyncParallelBailHook: new AsyncParallelBailHook(['arg1', 'arg2']), asyncSeriesHook: new AsyncSeriesHook(['arg1', 'arg2']), asyncSeriesBailHook: new AsyncSeriesBailHook(['arg1', 'arg2']), asyncSeriesWaterfallHook: new AsyncSeriesWaterfallHook(['arg1', 'arg2']) } } ... }初始化定义好了,那怎么去使用一个钩子呢,既然是
钩子的概念,就逃不过注册、触发这俩关键词,接下来我们看下Hook的使用方式
Hook 使用
- 同步钩子: 注册:
tap, 调用:call - 异步钩子: 注册:
tap, tapPromise, tapAsync, 调用方式:call, promise, callAsync - 示例:
1. SyncHook
class Car {
constructor(args) {
this.hooks = {
startHook: new SyncHook(),
syncBailHook: new SyncBailHook(),
syncWaterfallHook: new SyncWaterfallHook(['arg1', 'arg2']),
syncLoopHook: new SyncLoopHook(),
asyncParallelHook: new AsyncParallelHook(['arg1', 'arg2']),
asyncParallelBailHook: new AsyncParallelBailHook(['arg1', 'arg2']),
asyncSeriesHook: new AsyncSeriesHook(['arg1', 'arg2']),
asyncSeriesBailHook: new AsyncSeriesBailHook(['arg1', 'arg2']),
asyncSeriesWaterfallHook: new AsyncSeriesWaterfallHook(['arg1', 'arg2'])
}
}
start() {
this.hooks.startHook.call()
}
}
const carInstance = new Car()
// 注册
carInstance.hooks.startHook.tap('startPlugin1', () => {
console.log('trigger startPlugin1 callback')
})
carInstance.hooks.startHook.tap('startPlugin2', () => {
console.log('trigger startPlugin2 callback')
})
// 调用
carInstance.start()
输出结果如下:
可以看到同步钩子SyncHook会按照注册顺序按序执行
2. SyncBailHook
class Car {
,,,
bail() {
this.hooks.syncBailHook.tap()
}
}
,,,
// 注册
carInstance.hooks.syncBailHook.tap('syncBailPlugin1', () => {
console.log('trigger bailPlugin1 callback')
})
carInstance.hooks.syncBailHook.tap('syncBailPlugin2', () => {
console.log('trigger bailPlugin2 callback')
return 1
})
carInstance.hooks.syncBailHook.tap('syncBailPlugin3', () => {
console.log('trigger bailPlugin3 callback')
})
// 调用
carInstance.bail()
输出结果:
- 小结
SyncBailHook的特点是:回调函数中返回非undefined值时,钩子会停止执行。
如果将上述例子中syncBailPlugin2回调函数修改为return undefined, 则输出结果如下:
3. SyncWaterfallHook
class Car {
,,,
waterfall(arg1, arg2) {
this.hooks.syncWaterfallHook.call(arg1, arg2)
}
}
,,,
// 注册
carInstance.hooks.syncWaterfallHook.tap('syncWaterfallPlugin1', () => {
console.log('triggered syncWaterfallPlugin1 and return value 1')
return 1
})
carInstance.hooks.stncWaterfallHook.tap('syncWaterfallPlugin2', (prev) => {
console.log(`triggered syncWaterfallPlugin2, receive prev result: ${prev} and return ++prev`)
return ++prev
})
carInstance.hooks.stncWaterfallHook.tap('syncWaterfallPlugin3', (prev) => {
console.log(`triggered syncWaterfallPlugin3, receive prev result: ${prev} and return ${++prev}`)
return ++prev
})
// 调用
carInstance.hooks.waterfall()
输出结果:
再看一个有意思的情况,改动syncWaterfallPlugin2 的回调函数为:
carInstance.hooks.stncWaterfallHook.tap('syncWaterfallPlugin2', (prev) => {
console.log(`triggered syncWaterfallPlugin2, receive prev result: ${prev} and return undefined`)
return undefined
})
得到的结果是:
上述例子中,我们调用
waterfall的时候并没有初始化arg1和arg2,我们换一种调用方式看看:
carInstance.hooks.syncWaterFallHook.tap('syncWaterfallPlugin1', (arg1, arg2) => {
console.log(`triggered syncWaterfallPlugin1, arg1: ${arg1}, arg2: ${arg2}`)
arg2 = 3
return 2
})
carInstance.hooks.syncWaterFallHook.tap('syncWaterfallPlugin2', (arg1, arg2) => {
console.log(`triggered syncWaterfallPlugin2, arg1: ${arg1}, arg2: ${arg2}`)
return arg2
// console.log(`triggered syncWaterfallPlugin2, receive prev result: ${prev} and return undefined`)
// return undefined
})
carInstance.hooks.syncWaterFallHook.tap('syncWaterfallPlugin3', (arg1, arg2) => {
console.log(`triggered syncWaterfallPlugin3, arg1: ${arg1}, arg2: ${arg2}`)
return ++arg1
})
carInstance.waterfall(1, 'second')
输出结果:
-
小结
- 可以看到
SyncWaterfallHook瀑布钩子会传递最近的上一个返回值为非undefined钩子的返回值作为下一个钩子的参数 SyncWaterfallHook钩子多个参数时,只能修改第一个参数,修改方式不是赋值,而是依赖每个钩子的返回值,每个钩子的非undefined返回值会作为第一个参数的最新值
- 可以看到
4. SyncLoopHook
class Car {
,,,
syncLoop() {
this.hooks.syncLoopHook.call()
}
}
// 注册
let index = 1
carInstance.hooks.syncLoopHook.tap(`syncLoopHookPlugin1`, () => {
console.log(`trggered syncLoopPlugin1 启动: ${index}次`)
if (index < 4) {
index++
return index
}
})
// 调用
carInstance.syncLoop()
输出的结果是:
再看一个🌰:
,,,
// 注册
let index = 1
carInstance.hooks.syncLoopHook.tap(`syncLoopHookPlugin1`, () => {
console.log(`trggered syncLoopPlugin1 启动: ${index}次`)
if (index < 4) {
index++
return index
}
})
carInstance.hooks.syncLoopHook.tap('syncLoopHookPlugin2', () => {
console.log(`triggered syncLoopHookPlugin2 ${index} 次`)
index = 2
if (index < 4) {
index++
return index
}
})
carInstance.hooks.syncLoopHook.tap('syncLoopHookPlugin3', () => {
console.log('triggered syncLoopHookPlugin3')
})
// 调用
carInstance.syncLoop()
上个例子中我们注册了多个syncLoopHook,且动态去修改了index 的值,那我们看下输出结果是:
可以看到由于我们在syncLoopHookPlugin2的回调函数中修改了index的值,得到的结果就是 syncLoopPlugin1 和 syncLoopHookPlugin2 循环调用
- 小结
SyncLoopHook同步循环钩子会在任何一个被监听的函数中返回非 undefined值时,返回重头开始执行。 (这也是我们看到上述例子中并没有触发执行syncLoopHookPlugin3)
5. AsyncParallelHook
class Car {
,,,
asyncParallelHookCallFun(arg1, arg2, cb) {
this.hooks.asyncParallelHook.callAsync(arg1, arg2, cb)
// return this.hooks.asyncParallelHook.promise()
}
}
// 注册
console.time('timer')
carInstance.hooks.asyncParallelHook.tapAsync('asyncParallelPlugin1', (arg1, arg2, callback) => {
setTimeout(_ => {
console.log('triggered AsyncParallelPlugin1', arg1, arg2)
callback()
}, 1000)
})
carInstance.hooks.asyncParallelHook.tapPromise('asyncParallelPlugin2', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsyncParallelPlugin2', arg1, arg2)
resolve(true)
}, 1000)
})
})
carInstance.hooks.asyncParallelHook.tapPromise('asyncParallelPlugin3', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsyncParallelPlugin3', arg1, arg2)
resolve(false)
}, 1000)
})
})
carInstance.asyncParallelHookCallFun(1,2, () => {
console.log('AsyncParallelHooks 全部执行完毕')
console.timeEnd('timer')
})
输出结果:
修改下上述asyncParallelPlugin2的回调函数:
,,,
carInstance.hooks.asyncParallelHook.tapPromise('asyncParallelPlugin2', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsyncParallelPlugin2', arg1, arg2)
reject(true)
}, 1000)
})
})
则输出结果变为:
- 小结
AsyncParallelHook异步并行钩子,会在被监听函数中,截止到最先满足条件的钩子执行完后触发执行(并不一定是所有钩子都执行完了),但即使回调函数执行完,后续未执行的钩子也会继续执行
6. AsyncParallelBailHook
class Car {
asyncParallelBailHookCallFunc(arg1, arg2, cb) {
this.hooks.asyncParallelBailHook.callAsync(arg1, arg2, cb)
// return this.hooks.asyncParallelBailHook.promise()
}
}
carInstance.hooks.asyncParallelBailHook.tapPromise('asyncParallelBailPlugin1', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered asyncParallelBailPlugin1', arg1, arg2)
resolve(undefined)
}, 1000)
})
})
// 注册
carInstance.hooks.asyncParallelBailHook.tapAsync('asyncParallelBailPlugin2', (arg1, arg2) => {
setTimeout(_ => {
console.log('triggered asyncParallelBailPlugin2', arg1, arg2)
}, 1000)
})
// 调用
carInstance.asyncParallelBailHookCallFunc(1, 2, () => {
console.log('AsyncParallelHooks 全部执行完毕')
console.timeEnd('timer')
})
输出结果:
由于我们第一个
asyncParallelBailPlugin1 返回的是 resolve(undefind),所有会继续执行,如果修改为resolve(true), 那么输出结果为:
可以看到回调函数会在第一个钩子执行完后触发,但是第二个钩子也会继续执行
- 小结
AsyncParallelBailHook异步并行保释钩子,可以理解为保险丝,当返回undefined值时,会继续执行,返回非undefined值时,会触发最终回调函数的执行
7. AsyncSeriesHook
class Car {
asyncSeriesHookCallFunc(arg1, arg2) {
this.hooks.asyncSeriesHook.tapPromise(arg1, arg2)
}
}
// 注册
carInstance.hooks.asyncSeriesHook.tapPromise('asyncSeriesHookPlugin1', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('triggered asyncSeriesHookPlugin1', arg1, arg2)
resolve(6)
}, 1000);
})
})
carInstance.hooks.asyncSeriesHook.tapPromise('asyncSeriesHookPlugin1', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered asyncSeriesHookPlugin1', arg1, arg2)
resolve(5)
}, 1000)
})
})
// 调用
carInstance.asyncSeriesHookCallFunc(1, 2).then(res => {
console.log('全部执行完毕, ', res)
console.timeEnd('timer')
})
输出结果
如果将上述例子修改为
,,,
carInstance.hooks.asyncSeriesHook.tapPromise('asyncSeriesHookPlugin1', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('triggered asyncSeriesHookPlugin1', arg1, arg2)
reject(false)
}, 2000);
})
})
carInstance.hooks.asyncSeriesHook.tapPromise('asyncSeriesHookPlugin1', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered asyncSeriesHookPlugin1', arg1, arg2)
resolve(5)
}, 1000)
})
})
carInstance.asyncSeriesHookCallFunc(1, 2).then(res => {
console.log('全部执行完毕, ', res)
console.timeEnd('timer')
}).catch(err => {
console.log('catch series hooks error', err)
})
则输出结果为
- 小结
AsyncSeriesHook异步串行钩子会根据声明顺序串行执行,截止到reject状态或者tapAsync方式时的callback具备第一个参数表示错误,则终止执行(后钩子不会再执行)到最终回调函数的catch中。
8. AsyncSeriesBailHook
class Car {
,,,
asyncSeriesBailHookCallFunc(arg1, arg2, cb) {
this.hooks.asyncSeriesBailHook.callAsync(arg1, arg2, cb)
// return this.hooks.asyncSeriesBailHook.promise()
}
}
// 注册
carInstance.hooks.asyncSeriesBailHook.tapAsync('AsyncSeriesBailPlugin1', (arg1, arg2, callback) => {
setTimeout(() => {
console.log('triggered AsynsSeriesBailPlugin1 , args is: -- ', arg1, arg2)
callback()
}, 2000);
})
carInstance.hooks.asyncSeriesBailHook.tapPromise('AsyncSeriesBailPlugin2', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsynsSeriesBailPlugin2 , args is: -- ', arg1, arg2)
resolve()
}, 1000)
})
})
carInstance.hooks.asyncSeriesBailHook.tapPromise('AsyncSeriesBailPlugin3', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsynsSeriesBailPlugin3 , args is: -- ', arg1, arg2)
resolve(arg1)
}, 1000)
})
})
// 调用
carInstance.asyncSeriesBailHookCallFunc(1, 2, (result) => {
console.log('全部执行完毕', result)
console.timeEnd('timer')
})
输出结果是
如果将上述例子修改为:
carInstance.hooks.asyncSeriesBailHook.tapAsync('AsyncSeriesBailPlugin1', (arg1, arg2, callback) => {
setTimeout(() => {
console.log('triggered AsynsSeriesBailPlugin1 , args is: -- ', arg1, arg2)
callback(arg1)
}, 2000);
})
carInstance.hooks.asyncSeriesBailHook.tapPromise('AsyncSeriesBailPlugin2', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsynsSeriesBailPlugin2 , args is: -- ', arg1, arg2)
resolve()
}, 1000)
})
})
carInstance.hooks.asyncSeriesBailHook.tapPromise('AsyncSeriesBailPlugin3', (arg1, arg2) => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('triggered AsynsSeriesBailPlugin3 , args is: -- ', arg1, arg2)
resolve(arg1)
}, 1000)
})
})
carInstance.asyncSeriesBailHookCallFunc(1, 2, (result) => {
console.log('全部执行完毕', result)
console.timeEnd('timer')
})
则输出结果是:
- 小结
AsyncSeriesBailHook异步串行钩子,在callback或者resolve中返回非undefined值时,则终止执行。
9. AsyncSeriesWaterfallHook
class Car {
,,,
asyncSeriesWaterfallHookCallFunc(arg1, arg2, cb) {
return this.hooks.asyncSeriesWaterfallHook.promise(arg1, arg2, cb)
}
}
// 注册
carInstance.hooks.asyncSeriesWaterfallHook.tapAsync('AsyncSeriesWaterfallPlugin1', (arg1, arg2, cb) => {
setTimeout(_ => {
console.log('triggered AsyncSeriesWaterfallPlugin1', arg1, arg2)
cb()
}, 2000)
})
carInstance.hooks.asyncSeriesWaterfallHook.tapAsync('AsyncSeriesWaterfallPlugin2', (arg1, arg2, cb) => {
setTimeout(_ => {
console.log('triggered AsyncSeriesWaterfallPlugin2', arg1, arg2)
cb()
}, 1000)
})
// 调用
carInstance.asyncSeriesWaterfallHookCallFunc(1, 2, () => {
console.log('全部执行完毕')
console.timeEnd('timer')
})
输出结果:
- 小结
AsyncSeriesWaterfallHook异步瀑布钩子,无论监听的函数中是resolve还是reject都会继续执行,直至执行完毕触发回调函数
Hook 总结
- 同步:
SyncHook,SyncBailHook,SyncLoopHook,SyncWaterfallHook
下图是对Tapable的一个总结
对于MultiHook 和 HookMap都是辅助类,看下API调用就好
了解完Tapable,会结合去了解webpack内的插件价机制