tapable是webpack中的核心库,搞定它对理解webpack有很大的帮助。今天我们来看看tapable是一个啥东东。
首先我们可以通过npm install --save tapable将它下载下来,便于后续的学习调试和查看源代码。
tapable里面主要的实现是基于观察者模式,可以订阅/发布,但是有很多的执行机制,我们可以先来看看包含的主要钩子:
- const {
- SyncHook,
- SyncBailHook,
- SyncWaterfallHook,
- SyncLoopHook,
- AsyncParallelHook,
- AsyncParallelBailHook,
- AsyncSeriesHook,
- AsyncSeriesBailHook,
- AsyncSeriesWaterfallHook
- } = require("tapable");
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
这些钩子主要分为同步和异步,异步又分并行和串行。
SyncHook
SyncHook为同步执行,按照顺序执行注册的方法:
- const { SyncHook } = require("tapable");
- let syncHook = new SyncHook(["name", "age"]);
- syncHook.tap("1", (name, age) => console.log("1", name, age));
- syncHook.tap("2", (name, age) => console.log("2", name, age));
- syncHook.tap("3", (name, age) => console.log("3", name, age));
- syncHook.call("zhangsan", 20);
- 1 zhangsan 20
- 2 zhangsan 20
- 3 zhangsan 20
// SyncHook 钩子的使用
const { SyncHook } = require("tapable");
// 创建实例
let syncHook = new SyncHook(["name", "age"]);
// 注册事件
syncHook.tap("1", (name, age) => console.log("1", name, age));
syncHook.tap("2", (name, age) => console.log("2", name, age));
syncHook.tap("3", (name, age) => console.log("3", name, age));
// 触发事件,让监听函数执行
syncHook.call("zhangsan", 20);
/*结果*/
1 zhangsan 20
2 zhangsan 20
3 zhangsan 20
上面代码中,new SyncHook新建了一个同步执行的数组,通过tap方法往内部加入方法,通过call方法执行内部所有的方法。
new SyncHook中的参数(name,age)表示加入数组方法的参数,我们可以看到tap的第二个参数(加入的方法)中的方法参数都是name和age。参数在执行call方法的时候赋值。
tap中的第一个参数没什么具体的作用,相当于注解。
SyncBailHook
SyncBailHook也是同步执行,但是如果执行的时候遇到了一个不为空的返回,则不会继续执行:
- const { SyncBailHook } = require("tapable");
- let syncBailHook = new SyncBailHook(["name", "age"]);
- syncBailHook.tap("1", (name, age) => console.log("1", name, age));
- syncBailHook.tap("2", (name, age) => {
- console.log("2", name, age);
- return true;
- });
- syncBailHook.tap("3", (name, age) => console.log("3", name, age));
- syncBailHook.call("zhangsan", 20);
- 1 zhangsan 20
- 2 zhangsan 20
const { SyncBailHook } = require("tapable");
// 创建实例
let syncBailHook = new SyncBailHook(["name", "age"]);
// 注册事件
syncBailHook.tap("1", (name, age) => console.log("1", name, age));
syncBailHook.tap("2", (name, age) => {
console.log("2", name, age);
return true;
});
syncBailHook.tap("3", (name, age) => console.log("3", name, age));
// 触发事件,让监听函数执行
syncBailHook.call("zhangsan", 20);
/*结果*/
1 zhangsan 20
2 zhangsan 20
上面的第二个方法返回了true,不是undefined,因此中断执行。
SyncWaterfallHook
SyncWaterfallHook是同步执行,除了第一个方法外,后面方法的参数都来自前面方法的返回结果:
- const { SyncWaterfallHook } = require("tapable");
- let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);
- syncWaterfallHook.tap("1", (name, age) => {
- console.log("1", name, age);
- return "1";
- });
- syncWaterfallHook.tap("2", data => {
- console.log("2", data);
- return "2";
- });
- syncWaterfallHook.tap("3", data => {
- console.log("3", data);
- return "3"
- });
- let ret = syncWaterfallHook.call("zhangsan", 20);
- console.log("last:", ret);
- 1 zhangsan 20
- 2 1
- 3 2
- last: 3
const { SyncWaterfallHook } = require("tapable");
// 创建实例
let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);
// 注册事件
syncWaterfallHook.tap("1", (name, age) => {
console.log("1", name, age);
return "1";
});
syncWaterfallHook.tap("2", data => {
console.log("2", data);
return "2";
});
syncWaterfallHook.tap("3", data => {
console.log("3", data);
return "3"
});
// 触发事件,让监听函数执行
let ret = syncWaterfallHook.call("zhangsan", 20);
console.log("last:", ret);
/*结果*/
1 zhangsan 20
2 1
3 2
last: 3
我们可以看到,最后一个方法返回的结果就是最终的结果。
SyncLoopHook
SyncLoopHook是同步执行,如果一个方法返回的是undefined,那么继续执行下一个;如果返回的不是undefined,将从头开始再执行一次(之前执行过的也会重复执行):
- const { SyncLoopHook } = require("tapable");
- let syncLoopHook = new SyncLoopHook(["name", "age"]);
- let total2 = 0,total3=0;
- syncLoopHook.tap("1", (name, age) => {
- console.log("1", name, age);
- });
- syncLoopHook.tap("2", (name, age) => {
- console.log("2", name, age, total2);
- return total2++ < 2 ? true : undefined;
- });
- syncLoopHook.call("zhangsan", 20);
- 1 zhangsan 20
- 2 zhangsan 20 0
- 1 zhangsan 20
- 2 zhangsan 20 1
- 1 zhangsan 20
- 2 zhangsan 20 2
const { SyncLoopHook } = require("tapable");
// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);
// 定义辅助变量
let total2 = 0,total3=0;
// 注册事件
syncLoopHook.tap("1", (name, age) => {
console.log("1", name, age);
});
syncLoopHook.tap("2", (name, age) => {
console.log("2", name, age, total2);
return total2++ < 2 ? true : undefined;
});
// 触发事件,让监听函数执行
syncLoopHook.call("zhangsan", 20);
/*结果*/
1 zhangsan 20
2 zhangsan 20 0
1 zhangsan 20
2 zhangsan 20 1
1 zhangsan 20
2 zhangsan 20 2
我们可以看到,第一个方法应该是能立马通过的,但是却执行了3次,这是因为第二个方法卡住了之后,会从头执行。
AsyncParallelHook
AsyncParallelHook表示异步并行执行,可以通过tapAsync添加,callAsync触发;也可通过tapPromise添加,promise触发(tap/call属于同步,这里用不了),我们先看看tapAsync/callAsync版的:
- let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
- asyncParallelHook.tapAsync("1", (name, age, done) => {
- setTimeout(() => {
- console.log("1", name, age);
- done();
- }, 1000);
- });
- asyncParallelHook.tapAsync("2", (name, age, done) => {
- setTimeout(() => {
- console.log("2", name, age);
- done();
- }, 1000);
- });
- asyncParallelHook.callAsync("zhangsan", 20, () => {
- console.log("complete");
- });
- 1 zhangsan 20
- 2 zhangsan 20
- complete async
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
// tapAsync事件
asyncParallelHook.tapAsync("1", (name, age, done) => {
setTimeout(() => {
console.log("1", name, age);
done();
}, 1000);
});
asyncParallelHook.tapAsync("2", (name, age, done) => {
setTimeout(() => {
console.log("2", name, age);
done();
}, 1000);
});
asyncParallelHook.callAsync("zhangsan", 20, () => {
console.log("complete");
});
/*结果*/
1 zhangsan 20
2 zhangsan 20
complete async
上面的异步都是定时1秒钟,结果是同时输出内容,表示异步是同时被触发的,因此同时执行完毕。
在来看一个promise版本的:
- let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
- asyncParallelHook.tapPromise("1", (name, age) => {
- return new Promise((resolve,reject)=>{
- setTimeout(()=>{
- console.log('1',name,age);
- resolve('1');
- },1000);
- })
- });
- asyncParallelHook.tapPromise("2", (name, age, done) => {
- return new Promise((resolve,reject)=>{
- setTimeout(()=>{
- console.log('2',name,age);
- resolve('2');
- },1000);
- })
- });
- asyncParallelHook.promise("lisi", 21).then(rs=>{
- console.log('rs',rs);
- }).catch(er=>{
- console.log('err',er);
- })
- 1 lisi 21
- 2 lisi 21
- rs undefined
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
asyncParallelHook.tapPromise("1", (name, age) => {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('1',name,age);
resolve('1');
},1000);
})
});
asyncParallelHook.tapPromise("2", (name, age, done) => {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('2',name,age);
resolve('2');
},1000);
})
});
asyncParallelHook.promise("lisi", 21).then(rs=>{
console.log('rs',rs);
}).catch(er=>{
console.log('err',er);
})
/*结果*/
1 lisi 21
2 lisi 21
rs undefined
上面promise版的,如果中途有reject并不会中断剩下的执行,而是外部捕捉到错误。
AsyncSeriesHook
AsyncSeriesHook表示异步串行,先来看个tapAsync/callAsync版的:
- let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
- console.time("time");
- asyncSeriesHook.tapAsync("1", (name, age, next) => {
- setTimeout(() => {
- console.log("1", name, age);
- next();
- }, 1000);
- });
- asyncSeriesHook.tapAsync("2", (name, age, next) => {
- setTimeout(() => {
- console.log("2", name, age);
- next('2');
- }, 1000);
- });
- asyncSeriesHook.callAsync("panda", 18, (rs) => {
- console.log("complete",rs);
- });
- 1 panda 18
- 2 panda 18
- complete 2
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
// 注册事件
console.time("time");
asyncSeriesHook.tapAsync("1", (name, age, next) => {
setTimeout(() => {
console.log("1", name, age);
next();
}, 1000);
});
asyncSeriesHook.tapAsync("2", (name, age, next) => {
setTimeout(() => {
console.log("2", name, age);
next('2');
}, 1000);
});
// 触发事件,让监听函数执行
asyncSeriesHook.callAsync("panda", 18, (rs) => {
console.log("complete",rs);
});
/*结果*/
1 panda 18
2 panda 18
complete 2
两个异步都是1秒钟,结果是第一个先输出,第二个隔了1秒钟输出。如果想直接中断输出的话,可以在next中传入参数。
再来看个promise版:
- let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
- asyncSeriesHook.tapPromise("1", (name, age) => {
- return new Promise((resolve,reject)=>{
- setTimeout(()=>{
- console.log('1',name,age);
- resolve('1');
- },1000);
- });
- });
- asyncSeriesHook.tapPromise("2", (name, age) => {
- return new Promise((resolve,reject)=>{
- setTimeout(()=>{
- console.log('2',name,age);
- resolve('2');
- },1000);
- });
- });
- asyncSeriesHook.promise("zhangsan", 20).then((rs)=>{
- console.log('complete',rs);
- }).catch(ex=>{
- console.log('err',ex);
- });
- 1 zhangsan 20
- 2 zhangsan 20
- complete undefined
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
// 注册事件
asyncSeriesHook.tapPromise("1", (name, age) => {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('1',name,age);
resolve('1');
},1000);
});
});
asyncSeriesHook.tapPromise("2", (name, age) => {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('2',name,age);
resolve('2');
},1000);
});
});
// 触发事件,让监听函数执行
asyncSeriesHook.promise("zhangsan", 20).then((rs)=>{
console.log('complete',rs);
}).catch(ex=>{
console.log('err',ex);
});
/*结果*/
1 zhangsan 20
2 zhangsan 20
complete undefined
注意这里如果promise执行了reject,会中断后续的输入。
还有几个异步的情况和之前同步的类似,这里就不重复说了,不过注意AsyncSeriesLoopHook钩子虽然源代码里面有,但是并没有公布出来,因此无法使用。
拦截器(interception)
拦截器是在钩子执行之前的方法,主要分为call,tap,loop,register,含义如下:
call:(...args) => void当你的钩子触发之前,(就是call()之前),就会触发这个函数,你可以访问钩子的参数.多个钩子执行一次
tap: (tap: Tap) => void 每个钩子执行之前(多个钩子执行多个),就会触发这个函数
loop:(...args) => void 这个在每一次循环的时候触发。
register:(tap: Tap) => 每添加一个Tap都会触发 .可以得到注册的方法,注意需要返回才可以正常执行。
看个demo感受下:
- let syncLoopHook = new SyncLoopHook(["name", "age"]);
- syncLoopHook.intercept({
- call: () => {
- console.log("call");
- },
- register: (tapInfo) => {
-
- console.log('register');
- return tapInfo;
- },
- tap: (tap) => {
- console.log('tap')
- },
- loop: () => {
- console.log('loop');
- }
- })
- let total2 = 0,
- total3 = 0;
- syncLoopHook.tap("1", (name, age) => {
- console.log("1", name, age);
- });
- syncLoopHook.tap("2", (name, age) => {
- console.log("2", name, age, total2);
- return total2++ < 2 ? true : undefined;
- });
- syncLoopHook.call("zhangsan", 20);
- register
- register
- call
- loop
- tap
- 1 zhangsan 20
- tap
- 2 zhangsan 20 0
- loop
- tap
- 1 zhangsan 20
- tap
- 2 zhangsan 20 1
- loop
- tap
- 1 zhangsan 20
- tap
- 2 zhangsan 20 2
// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);
syncLoopHook.intercept({ //拦截器
call: () => {
console.log("call");
},
register: (tapInfo) => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
console.log('register');
return tapInfo; // may return a new tapInfo object
},
tap: (tap) => {
console.log('tap')
},
loop: () => {
console.log('loop');
}
})
// 定义辅助变量
let total2 = 0,
total3 = 0;
// 注册事件
syncLoopHook.tap("1", (name, age) => {
console.log("1", name, age);
});
syncLoopHook.tap("2", (name, age) => {
console.log("2", name, age, total2);
return total2++ < 2 ? true : undefined;
});
// 触发事件,让监听函数执行
syncLoopHook.call("zhangsan", 20);
/*结果*/
register
register
call
loop
tap
1 zhangsan 20
tap
2 zhangsan 20 0
loop
tap
1 zhangsan 20
tap
2 zhangsan 20 1
loop
tap
1 zhangsan 20
tap
2 zhangsan 20 2
Context(上下文)
可以设置context让插件或拦截器获得自定义内容:
- let syncHook = new SyncHook(["name", "age"]);
- syncHook.intercept({
- context: true,
- tap: (context,tap) => {
- if(context){
- context.sex='man';
- }
- }
- })
- syncHook.tap({
- name:'1',
- context:true
- }, (context,name, age) => {
- if(context){
- console.log(context,name,age);
- }
- });
- syncHook.call("zhangsan", 20);
- {sex: "man"} "zhangsan" 20
// 创建实例
let syncHook = new SyncHook(["name", "age"]);
syncHook.intercept({ //拦截器
context: true,
tap: (context,tap) => {
if(context){
context.sex='man';
}
}
})
// 注册事件
syncHook.tap({
name:'1',
context:true
}, (context,name, age) => {
if(context){
console.log(context,name,age);
}
});
// 触发事件,让监听函数执行
syncHook.call("zhangsan", 20);
/*结果*/
{sex: "man"} "zhangsan" 20
HookMap
HookMap是Hooks的帮助类,定义一个生成Hook的工厂(factory),如果相同的key得到同一个Hook:
- import{AsyncParallelHook} from 'tapable'
- const keyedHook = new HookMap(key => new AsyncParallelHook(["arg"]))
- keyedHook.tapAsync("AAA", "1", (arg, done) => {
- console.log('11');
- done();
- });
- keyedHook.tapAsync("AAA", "2", (arg, done) => {
- console.log('22');
- done();
- });
- const hook = keyedHook.get("AAA");
- if(hook !== undefined) {
- hook.callAsync("arg", () => {
- console.log('finish');
- });
- }
- 11
- 22
- finish
import{AsyncParallelHook} from 'tapable'
const keyedHook = new HookMap(key => new AsyncParallelHook(["arg"]))
keyedHook.tapAsync("AAA", "1", (arg, done) => {
console.log('11');
done();
});
keyedHook.tapAsync("AAA", "2", (arg, done) => {
console.log('22');
done();
});
const hook = keyedHook.get("AAA");
if(hook !== undefined) {
hook.callAsync("arg", () => {
console.log('finish');
});
}
/*结果*/
11
22
finish
MultiHook
MultiHook提供一种数组形式的Hook处理方式:
- import {SyncHook} from 'tapable'
- const multiHook = new MultiHook([new SyncHook(['name']),new SyncHook('name')]);
- multiHook.tap("1", (name) => {
- console.log('1',name);
- });
- multiHook.tap("2", (name) => {
- console.log('2',name);
- });
- multiHook.hooks[0].call('zhangsan');
- 1 zhangsan
- 2 zhangsan
import {SyncHook} from 'tapable'
const multiHook = new MultiHook([new SyncHook(['name']),new SyncHook('name')]);
multiHook.tap("1", (name) => {
console.log('1',name);
});
multiHook.tap("2", (name) => {
console.log('2',name);
});
multiHook.hooks[0].call('zhangsan');
/*结果*/
1 zhangsan
2 zhangsan