前言
为什么我们要学tapable,因为....webpack源码里面都是用的tapable来实现钩子挂载的,作为一个有点追求的code,webpack怎么能只满足于用呢?当然是要去看源码,写loader,plugin啦.在这之前,要是不清楚tapable的用法,源码那是更不用看了,看不懂.....所以,今天来讲一下tapable吧
1. tapable
webpack本质上是一种事件流的机制,他的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建的bundles的Compilation都是Tapable的实例
tapable创建实例时传递的参数对于程序运行并没有任何作用,只是给源码阅读者提供帮助
同样的,在使用tap*注册监听时,传递的第一个参数,也只是一个标识,并不会在程序运行中产生任何影响。而第二个参数则是回调函数
2.tapable的用法
const {
SyncHook,
SyncBailHook,
SyncWaterHook,
SyncLoopHook
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
复制代码
序号 | 钩子名称 | 执行方式 | 使用要点 |
---|---|---|---|
1 | SyncHook | 同步串行 | 不关心监听函数的返回值 |
2 | SyncBailHook | 同步串行 | 只要监听函数中有一个函数的返回值不为null,则跳过剩余逻辑 |
3 | SyncWaterfallHook | 同步串行 | 上一个监听函数的返回值将作为参数传递给下一个监听函数 |
4 | SyncLoopHook | 同步串行 | 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环 |
5 | AsyncParallelHook | 异步并行 | 不关心监听函数的返回值 |
6 | AsyncParallelBailHook | 异步并行 | 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数 |
7 | AsyncSeriesHook | 异步串行 | 不关心callback()的参数 |
8 | AsyncSeriesBailHook | 异步串行 | callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数 |
9 | AsyncSeriesWaterfallHook | 异步串行 | 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数 |
3. Sync*类型的钩子
- 注册在该钩子下面的插件的执行顺序都是顺序执行
- 只能使用tap注册,不能使用tapPromise和tapAsync注册
3.1 SyncHook
串行同步执行,不关心返回值 在SyncHook的实例上注册了tap之后,只要实例调用了call方法,那么这些tap的回掉函数一定会顺序执行一遍
let queue = new SyncHook(['没任何作用的参数']);
queue.tap(1,(name,age)=>{
console.log(name,age)
})
queue.tap(2,(name,age)=>{
console.log(name,age)
})
queue.tap(3,(name,age)=>{
console.log(name,age)
})
queue.call('bearbao',8)
// 输出结果
// 'bearbao' 8
// 'bearbao' 8
// 'bearbao' 8
复制代码
3.1.1 SyncHook实现
class SyncHook {
constructor(){
this.listeners = [];
}
tap(formal,listener){
this.listeners.push(listener)
}
call(...args){
this.listeners.forEach(l=>l(...args))
}
}
复制代码
3.2 SyncBailHook
串行同步执行,有一个返回值不为null则跳过剩下的逻辑
let queue = new SyncBailHook(['name'])
queue.tap(1,name=>{
console.log(name)
})
queue.tap(1,name=>{
console.log(name)
return '1'
})
queue.tap(1,name=>{
console.log(name)
})
queue.call('bearbao')
// 输出结果,只执行前面两个回调,第三个不执行
// bearbao
// bearbao
复制代码
实现
class SyncBailHook {
constructor(){
this.listeners = [];
}
tap(formal,listener){
this.listeners.push(listener)
}
call(...args){
for(let i=0;i<this.listeners.length;i++){
if(this.listeners[i]()) break;
}
}
}
复制代码
3.3 SyncWaterHook
串行同步执行,第一个注册的回调函数会接收call传进来的所有参数,之后的每个回调函数只接收到一个参数,就是上一个回调函数的返回值.
let queue = new SyncWaterHook(['name','age']);
queue.tap(1,(name,age)=>{
console.log(name,age)
return 1
})
queue.tap(2,(ret)=>{
console.log(ret)
return 2
})
queue.tap(3,(ret)=>{
console.log(ret)
return 3
})
queue.call('bearbao', 3)
// 输出结果
// bearbao 3
// 1
// 2
复制代码
SyncWaterHook 实现. SyncWaterHook这个方法很像redux中的compose方法,都是将一个函数的返回值作为参数传递给下一个函数.
对下面实现的call方法如果有疑惑,看不大懂的同学可以移步我之前对于compose函数的解读,里面有详细的介绍,这里就不多加赘述了
class SyncWaterHook{
constructor(){
this.listeners = [];
}
tap(formal,listener){
this.listener.unshift(listener);
}
call(...args){
this.listeners.reduce((a,b)=>(...args)=>a(b(...args)))(...args)
}
}
复制代码
3.4 SyncLoopHook
串行同步执行, 监听函数返回true表示继续循环,返回undefined表示循环结束
let queue = new SyncLoopHook;
let index = 0;
queue.tap(1,_=>{
index++
if(index<3){
console.log(index);
return true
}
})
queue.call();
// 输出结果
// 1
// 2
复制代码
SyncLoopHook实现
class SyncLoopHook{
constructor() {
this.tasks=[];
}
tap(name,task) {
this.tasks.push(task);
}
call(...args) {
this.tasks.forEach(task => {
let ret=true;
do {
ret = task(...args);
}while(ret)
});
}
}
复制代码
4. Async*类型的钩子
- 支持tap、tapPromise、tapAsync注册
- 每次都是调用tap、tapSync、tapPromise注册不同类型的插件钩子,通过调用call、callAsync 、promise方式调用。其实调用的时候为了按照一定的执行策略执行,调用compile方法快速编译出一个方法来执行这些插件。
4.1 AsyncParallel
异步并行执行
4.1.1 AsyncParallelHook
不关心监听函数的返回值.
有三种注册/发布的模式,如下
异步订阅 | 调用方法 |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
- 通过tap来使用
触发函数的参数,出了最后一个参数是异步监听回调函数执行完成之后的回调,其他的参数都是传递给回调函数的参数
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tap('1',function(name){
console.log(name,1);
});
queue.tap('2',function(name){
console.log(name,2);
});
queue.tap('3',function(name){
console.log(name,3);
});
queue.callAsync('bearbao',err=>{
console.log(err);
console.timeEnd('cost');
});
// 执行结果
/*
bearbao 1
bearbao 2
bearbao 3
cost: 4.720ms
*/
复制代码
实现
class AsyncParallelHook {
constructor(){
this.listeners = [];
}
tap(name,listener){
this.listeners.push(listener);
}
callAsync(){
this.listeners.forEach(listener=>listener(...arguments));
Array.from(arguments).pop()();
}
}
复制代码
- 通过tapAsync来注册
注意,这里有个特殊的地方,如何确认某个回调执行完了呢?,每个监听回调的最后一个参数是一个回调函数,当执行callback之后,会认为当前函数执行完毕
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
setTimeout(function(){
console.log(name, 1);
callback();
},1000)
});
queue.tapAsync('2',function(name,callback){
setTimeout(function(){
console.log(name, 2);
callback();
},2000)
});
queue.tapAsync('3',function(name,callback){
setTimeout(function(){
console.log(name, 3);
callback();
},3000)
});
queue.callAsync('bearbao',err=>{
console.log(err);
console.timeEnd('cost');
});
// 输出结果
/*
bearbao 1
bearbao 2
bearbao 3
cost: 3000.448974609375ms
*/
复制代码
实现
class AsyncParallelHook {
constructor(){
this.listeners = [];
}
tapAsync(name,listener){
this.listeners.push(listener);
}
callAsync(...arg){
let callback = arg.pop();
let i = 0;
let done = ()=>{
if(++i==this.listeners.length){
callback()
}
}
this.listeners.forEach(listener=>listener(...arg,done));
}
}
复制代码
- 使用tapPromise
使用tapPromise注册监听时,每个回调函数的返回值必须是一个Promise的实例
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(1);
resolve();
},1000)
});
});
queue.tapPromise('2',function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(2);
resolve();
},2000)
});
});
queue.tapPromise('3',function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(3);
resolve();
},3000)
});
});
queue.promise('bearbao').then(()=>{
console.timeEnd('cost');
})
// 执行记过
/*
1
2
3
cost: 3000.448974609375ms
*/
复制代码
实现
class AsyncParallelHook {
constructor(){
this.listeners = [];
}
tapPromise(name,listener){
this.listeners.push(listener);
}
promise(...arg){
let i = 0;
return Promise.all(this.listeners.map(l=>l(arg)))
}
}
复制代码
5. 好困好困
一不小心又到1点了,为了能够获得长寿成就,今天就先写到这里吧,后续几个方法,过两天再更新上来
结语
如果觉得还可以,能在诸君的编码之路上带来一点帮助,请点赞鼓励一下,谢谢!