webpack的插件本质上是基于事件流的机制,它的工作流程就是将各个插件串连起来,而实现这一切的核心就是Tabable。Tabable有点类似于nodejs的EventEmitter库,核心原理也是依赖发布订阅模式。
Tapable
Tabable提供了很多钩子类,可大致分为同步(sync*)和异步(async*)两种
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Tapable
安装
yarn add tapable
见个面
所有的钩子构造函数都接受一个可选的参数(这个参数最好是数组,不是数组tapable内部也把他变成数组),这是一个参数的字符串名字列表。下面以简单的同步钩子为例,带大家先体验一下tapable的基本使用
let { SyncHook } = require('Tapable')
let hook = new SyncHook(['name']) //通过SyncHook实例化一个同步钩子,参数可选,最好传数组
hook.tap('hello',function(name){ //注册函数
console.log('hello ', name)
})
hook.tap('welcome',function(name){ //注册函数
console.log('welcome', name)
})
hook.call('word') //触发函数
//hello word
//welcome word
在案例中,我们通过SyncHook
构建了一个同步钩子,然后调用tap
注册了两个函数,再调用call
依次触发注册的回调函数。
tap
用于绑定同步钩子的API,绑定异步钩子需要使用tapAsync
(绑定异步钩子的API)和tapPromise
(绑定promise钩子的API)
使用与实现
下面将介绍以下几个同步和异步钩子的使用,再简单的实现一下
特点概览
- 同步钩子(sync*)
- SyncHook:
同步串行,不关心订阅函数执行后的返回值是什么,在触发事件之后,会按照事件注册的先后顺序执行所有的事件处理函数。
- SyncBailHook:
同步串行,执行过程中注册的回调返回非 undefined 时就停止不在执行(Bai保险)
- SyncWaterfallHook:
同步串行瀑布流,上一个注册的回调返回值会作为下一个注册的回调的参数
- SyncLoopHook:
同步串行,在执行过程中回调返回非 undefined 时继续再次执行当前的回调
- SyncHook:
- 异步钩子(async*)
- AsyncParallelHook:
异步并行,当注册的所有异步回调都并行执行完毕之后再执行 callAsync 或者 promise 中的函数
- AsyncSeriesHook:
异步串行,顺序的执行异步函数
- AsyncParallelBailHook:
异步并行,执行过程中注册的回调返回非 undefined 时就会直接执行 callAsync 或者 promise 中的函数(由于并行执行的原因,注册的其他回调依然会执行)
- AsyncSeriesBailHook:
异步串行,执行过程中注册的回调返回非 undefined 时就会直接执行 callAsync 或者 promise 中的函数,并且注册的后续回调都不会执行
- AsyncSeriesWaterfallHook:
异步串行瀑布流,与 SyncWaterfallHook 类似,上一个注册的异步回调执行之后的返回值会传递给下一个注册的回调
- AsyncParallelHook:
下面将分别引入Tapable库的这几个同步和异步钩子,查看执行结果,并着手实现一下相同的效果
同步钩子
SyncHook
- 特点:同步串行,不关心订阅函数执行后的返回值是什么,在触发事件之后,会按照事件注册的先后顺序执行所有的事件处理函数。其原理是将监听(订阅)的函数存放到一个数组中, 发布时遍历数组中的监听函数并且将发布时的 arguments传递给监听函数
- 使用:(请参考前面[见个面])
- 实现:
class SyncHook {
constructor(args){
//记录添加的hook(回调函数)
this.hooks = []
}
tap(name,hook){
//注册回调函数
this.hooks.push(hook)
}
call(...args){
//依次执行注册的回调函数
this.hooks.forEach(hook=>hook(...args))
}
}
// 使用
let hook = new SyncHook(['name']) //通过SyncHook实例化一个同步钩子,参数可选,最好传数组
hook.tap('hello',function(name){ //注册函数
console.log('hello ', name)
})
hook.tap('welcome',function(name){ //注册函数
console.log('welcome', name)
})
hook.call('word')
//hello word
//welcome word
SyncBailHook
- 特点:同步串行,执行过程中注册的回调返回非 undefined 时就停止不在执行(Bai保险)
- 使用:
let { SyncBailHook } = require('Tapable')
let hook = new SyncBailHook(['name'])
hook.tap('hello',function(name){
console.log('hello ', name)
return '好久不见!' // 返回值不为undefined时停止执行后面函数
})
hook.tap('welcome',function(name){
console.log('welcome', name)
})
hook.call('word')
//hello word
- 实现
class SyncBailHook{
constructor(args){
this.hooks = []
}
tap(name,hook){
this.hooks.push(hook)
}
call(...args){
if(this.hooks.length === 0){
return
}
let ret; //当前函数返回值
let index = 0; //从第一个还是开始执行
do {
ret = this.hooks[index++](...args)
} while (ret === undefined && index < this.hooks.length);
}
}
let hook = new SyncBailHook(['name'])
hook.tap('hello',function(name){
console.log('hello ', name)
return ''好久不见!' // 返回值不为undefined时停止执行后面函数
})
hook.tap('welcome',function(name){
console.log('welcome', name)
})
hook.call('word')
//hello word
SyncWaterfallHook
- 特点:同步串行瀑布流,上一个注册的回调返回值会作为下一个注册的回调的参数
- 使用:
let { SyncWaterfallHook } = require('Tapable')
let hook = new SyncWaterfallHook(['name'])
hook.tap('hello',function(name){
console.log('hello ', name)
return '好久不见!' // 返回值将作为后面的参数
})
hook.tap('welcome',function(name){
console.log('welcome', name) // 没用返回值,将返回参数
})
hook.tap('again',function(name){
console.log('again', name)
})
hook.call('word')
//hello word
//welcome 好久不见!
//again 好久不见!
- 实现
class SyncWaterfallHook{
constructor(args){
this.hooks = []
}
tap(name,hook){
this.hooks.push(hook)
}
call(...args){
if(this.hooks.length === 0){
return
}
let [first,...other] = this.hooks
let ret = first(...args)
other.reduce((a,b)=>{
let rest = b(a)
if(rest !== undefined){
return rest
}
return a
}, ret)
}
}
let hook = new SyncWaterfallHook(['name'])
hook.tap('hello',function(name){
console.log('hello ', name)
return '好久不见!' // 返回值将作为后面的参数
})
hook.tap('welcome',function(name){
console.log('welcome', name) // 没用返回值,将返回参数
})
hook.tap('again',function(name){
console.log('again', name)
})
hook.call('word')
//hello word
//welcome 好久不见!
//again 好久不见!
SyncLoopHook
- 特点:同步串行,在执行过程中回调返回非 undefined 时继续再次执行当前的回调
- 使用:
let { SyncLoopHook } = require('Tapable')
let hook = new SyncLoopHook(['name'])
let index = 0
hook.tap('hello',function(name){
console.log('hello ', name)
return ++index === 5 ? undefined : 'again'
})
hook.tap('welcome',function(name){
console.log('welcome', name)
})
hook.call('word')
//hello word
//hello word
//hello word
//hello word
//hello word
//welcome word
- 实现:
class SyncLoopHook{
constructor(args){
this.hooks = []
}
tap(name,hook){
this.hooks.push(hook)
}
call(...args){
this.hooks.forEach(hook=>{
let ret;
do {
ret = hook(...args)
} while (ret !== undefined);
})
}
}
let hook = new SyncLoopHook(['name'])
let index = 0
hook.tap('hello',function(name){
console.log('hello ', name)
return ++index === 5 ? undefined : 'again'
})
hook.tap('welcome',function(name){
console.log('welcome', name)
})
hook.call('word')
//hello word
//hello word
//hello word
//hello word
//hello word
//welcome word
异步钩子
AsyncParallelHook
- 特点:异步并行,当注册的所有异步回调都并行执行完毕之后再执行 callAsync 或者 promise 中的函数
- 使用:
// 后续
- 实现:
// 后续
AsyncSeriesHook
- 特点:异步串行,顺序的执行异步函数
- 使用:
// 后续
- 实现:
// 后续
AsyncParallelBailHook
- 特点:异步并行,执行过程中注册的回调返回非 undefined 时就会直接执行 callAsync 或者 promise 中的函数(由于并行执行的原因,注册的其他回调依然会执行)
- 使用:
// 后续
- 实现:
// 后续
AsyncSeriesBailHook
- 特点:异步串行,执行过程中注册的回调返回非 undefined 时就会直接执行 callAsync 或者 promise 中的函数,并且注册的后续回调都不会执行
- 使用:
// 后续
- 实现:
// 后续
AsyncSeriesWaterfallHook
- 特点:异步串行瀑布流,与 SyncWaterfallHook 类似,上一个注册的异步回调执行之后的返回值会传递给下一个注册的回调
- 使用:
// 后续
- 实现:
// 后续
网友评论