tapable钩子实现
分类:javascript
最近在阅读webpack源码的时候,发现在webpack的编译器(compiler.js)里用到了tapable,于是查资料学习了以下,这里做一个学习记录。
tapable是一个基于 发布·订阅 模式的hooks钩子库,类似node的EventEmitter,提供了很多同步/异步钩子函数供我们使用
同步hooks
SyncHook
普通同步钩子:通过tap注册事件到hook的事件列表,然后call调用依次执行注册的事件函数
const { SyncHook } = require('tapable')
class TapSyncHook {
constructor() {
this.hooks = {
log: new SyncHook(['name']),
say: new SyncHook(['name']),
}
}
tap() {
this.hooks.log.tap('node', (name) => {
console.log('node', name)
})
this.hooks.say.tap('react', (name) => {
console.log('react', name)
})
this.hooks.say.tap('vue', (name) => {
console.log('vue', name)
})
}
run(name) {
this.hooks.log.call(name)
this.hooks.say.call(name)
}
}
const tap = new TapSyncHook()
tap.tap()
tap.run('tuya.cn')
/**
* 执行结果:
* node tuya.cn
react tuya.cn
vue tuya.cn
*/
SyncBailHook
带保险的同步钩子:
- 事件回调函数返回的数据是undefined时,继续执行后面的事件回调;
- 事件回调函数返回的数据不是undefined时,跳过后面所有事件回调执行;
即:执行事件回调列表直到拿到非undefined值或者执行完所有事件回调
const { SyncBailHook } = require('tapable')
class TapSyncBailHook {
constructor() {
this.hooks = {
log: new SyncBailHook(['name']),
}
}
tap() {
this.hooks.log.tap('node', (name) => {
console.log('node', name)
})
this.hooks.log.tap('react', (name) => {
console.log('react', name)
return 'react'
})
this.hooks.log.tap('vue', (name) => {
console.log('vue', name)
})
}
run(name) {
this.hooks.log.call(name)
}
}
const tap = new TapSyncBailHook()
tap.tap()
tap.run('tuya.cn')
/**
* 执行结果:
* node tuya.cn
react tuya.cn
*/
SyncWaterfallHook
“瀑布”同步钩子:
- 事件回调依次执行,上一个回调返回的数据作为下一个函数的入参被使用;
- 如果上一个事件执行没有返回数据,则依次往上找,直到找到有返回的数据或者初始数据;
const { SyncWaterfallHook } = require('tapable')
class TapSyncWaterfallHook {
constructor() {
this.hooks = {
log: new SyncWaterfallHook(['name']),
}
}
tap() {
this.hooks.log.tap('node', (name) => {
console.log('node', name)
return '返回数据'
})
this.hooks.log.tap('react', (data) => {
console.log('react', data)
})
this.hooks.log.tap('vue', (data) => {
console.log('vue', data)
return 'vue'
})
this.hooks.log.tap('js', (data) => {
console.log('js', data)
})
}
run(name) {
this.hooks.log.call(name)
}
}
const tap = new TapSyncWaterfallHook()
tap.tap()
tap.run('tuya.cn')
/**
* 执行结果:
* node tuya.cn
react 返回数据
vue 返回数据
js vue
*/
SyncLoopHook
循环同步钩子:
- 事件回调返回undefined时,继续执行剩下的事件回调;
- 事件回调返回的不是undefeated时,死循环执行当前事件函数
const { SyncLoopHook } = require('tapable')
class TapSyncLoopHook {
constructor() {
this.hook = {
log: new SyncLoopHook(['name']),
}
this.loop = 3
}
tap() {
this.hook.log.tap('node', (name) => {
console.log('node', name)
return --this.loop === 0 ? undefined : 'node'
})
this.hook.log.tap('react', (name) => {
console.log('react', name)
})
this.hook.log.tap('vue', (name) => {
console.log('vue', name)
})
}
run(...args) {
this.hook.log.call(...args)
}
}
const hook = new TapSyncLoopHook()
hook.tap()
hook.run('tuya.cn')
//第一个事件函数执行3次
/**
* 执行结果:
* node tuya.cn
node tuya.cn
node tuya.cn
react tuya.cn
vue tuya.cn
*/
异步hooks
异步钩子分为异步并发(Parallel)执行和异步串行(Series)执行,从hook名字我们就可以看出来。
所有异步钩子都有callback和promise写法,这里只举例callback写法
AsyncParallelHook
并发异步钩子:
- 各事件函数并发执行;
- 与同步钩子不同,采用tapAsync注册事件,callAsync执行事件函数,且回调函数多一个callback参数,用来告知hook当前回调函数执行完成了;
- 若其中一个函数未执行完(callback未被调用),callAsync的回调都不会执行
const { AsyncParallelHook } = require('tapable')
class TapAsyncParallelHook {
constructor() {
this.hooks = {
log: new AsyncParallelHook(['name']),
}
}
tap() {
this.hooks.log.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name)
cb()
}, 1000)
})
this.hooks.log.tapAsync('react', (data, cb) => {
setTimeout(() => {
console.log('react', data)
cb()
}, 1000)
})
this.hooks.log.tapAsync('vue', (data, cb) => {
setTimeout(() => {
console.log('vue', data)
cb()
}, 1000)
})
}
run(name) {
this.hooks.log.callAsync(name, () => {
console.log('end')
})
}
}
const tap = new TapAsyncParallelHook()
tap.tap()
tap.run('tuya.cn')
/**
* 执行结果:
* node tuya.cn
react tuya.cn
vue tuya.cn
end
*/
ASyncSeriesHook
异步串行钩子:
- 各事件函数串性执行,其余的特性与AsyncParallelHook一致
const { AsyncSeriesHook } = require('tapable')
class TapAsyncSeriesHook {
constructor() {
this.hooks = {
log: new AsyncSeriesHook(['name']),
}
}
tap() {
this.hooks.log.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name)
cb()
}, 1000)
})
this.hooks.log.tapAsync('react', (data, cb) => {
setTimeout(() => {
console.log('react', data)
cb()
}, 1000)
})
this.hooks.log.tapAsync('vue', (data, cb) => {
setTimeout(() => {
console.log('vue', data)
cb()
}, 1000)
})
}
run(name) {
this.hooks.log.callAsync(name, () => {
console.log('end')
})
}
}
const tap = new TapAsyncSeriesHook()
tap.tap()
tap.run('tuya.cn')
/**
* 执行结果:
* node tuya.cn
react tuya.cn
vue tuya.cn
end
*/
ASyncSeriesWaterfallHook
异步串行“瀑布”钩子:
- 各事件函数串性执行;
- 执行完成后调用回调callback,callback接收两个入参,第一个标记是否执行后面的事件,第二个参数是传入下一个事件函数的数据;
- 若callback的第一个参数是“error”,直接跳过后面的所有事件函数执行,执行callAsync的callback(不传默认是error);
const { AsyncSeriesWaterfallHook } = require('tapable')
class TapAsyncSeriesWaterfallHook {
constructor() {
this.hooks = {
log: new AsyncSeriesWaterfallHook(['name']),
}
}
tap() {
this.hooks.log.tapAsync('node', (name, cb) => {
setTimeout(() => {
console.log('node', name)
cb(null, 'node')
}, 1000)
})
this.hooks.log.tapAsync('react', (data, cb) => {
setTimeout(() => {
console.log('react', data)
cb(null, 'react')
}, 1000)
})
this.hooks.log.tapAsync('vue', (data, cb) => {
setTimeout(() => {
console.log('vue', data)
cb()
}, 1000)
})
}
run(name) {
this.hooks.log.callAsync(name, () => {
console.log('end')
})
}
}
const tap = new TapAsyncSeriesWaterfallHook()
tap.tap()
tap.run('tuya.cn')
/**
* 执行结果:
* node tuya.cn
react node
vue react
end
*/
总结:
- tapable提供tap注册同步事件,tapAsync,tapPromise注册异步事件;
- tapable提供call调用同步事件,callAsync,promise调用异步事件;
以上钩子均手写实现,github查看