了解webpack Plugin之前应该掌握Tapable
在 Webpack 的编译过程中,本质上通过 Tapable 实现了在编译过程中的一种发布订阅者模式的插件 Plugin 机制。
- Tapable提供了一系列事件的发布订阅者API,通过Tapable使用者可以注册事件,从而在不同实际去触发注册的事件进行执行
官方提供的九种钩子
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
- AsyncParallelHook
- AsyncParallelBaiHook
- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
Tapable中所有注册的事件可以分为同步、异步两种执行方式
- 同步: 表示注册的事件函数会同步进行执行(Sync前缀)
- 异步: 表示注册的事件函数会异步进行执行(Async前缀)
- 异步串行钩子(AsyncSeries):可以被串联(连续按照顺序调用)执行的异步钩子函数
- 异步并行钩子(AsyncParalle):可以被并联(并发调用)执行的异步钩子函数
- 同步钩子唯一的注册事件的方法是tap,通过call方法调用执行
- 异步钩子可以通过tap、tapAsync、tapPromise三种方式来注册,通过对应的call、callAsync、promise三种方式来触发注册的函数
Tapable 按照执行机制分类
- Basic Hook:基本类型钩子,仅执行钩子注册的事件,不关心被调用事件的返回值
- Waterfall:瀑布类型的钩子,会在注册事件执行时将事件函数执行非undefined的返回值传递给接下来的事件函数作为参数,其他的与基本类型钩子类似
- Bail:保险类型钩子,在基础类型钩子上增加一个保险机制,如果任意一个注册函数执行返回非undefined>的值,那么整个钩子执行过程会立即中断,之后的注册事件就不会调用
- Loop: 循环类型钩子,通过 call 调用时,如果任意一个注册的事件函数返回值非 undefeind,那么会立即重头开始重新执行所有的注册事件函数,直到所有被注册的事件函数都返回 undefined。
- 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const {SyncHook, SyncBailHook, SyncWaterfallHook,SyncWaterfallHook } = require('tapable')
const hook = new SyncHook(['arg1','arg2','arg3']) // 这个就换钩子名,其他一样
# SyncHook
// 初始化同步钩子
重点:数组中对应的字符串个数
// 注册事件
▫️▪ 通过tap函数监听对应的事件,注册事件时接受两个参数:
▫️▪ ️第一个参数是 `字符串 | 对象`,`仅作标识`
▫️▪ 第二个参数表示`本次注册的函数`,在调用时会执行这个函数
hook.tap('flag1',(arg1,arg2,arg3) => {})
// 调用事件并传递执行参数
▫️▪ 通过call方法传入对应的参数,调用注册在hook内部的事件函数执行,同时将call方法`传入的参数传递`给`每一个`注册的`事件`函数作为`实参`进行调用
hook.call('1','2','3')
# SyncBailHook
// 注册事件
hook.tap('flag1',(arg1,arg2,arg3) =>{
// 存在返回值,阻断后面注册事件的调用
return true
})
//调用事件
hook.call('1','2','3') - 参考
Tapable讲解
拦截器
- Tapable 提供的所有 Hook 都支持注入 Interception ,可以通过拦截器对整个Tapable发布/订阅流程进行监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14hook.intercept({
// 每次通过 tap、tapAsync、tapPromise 方法注册事件函数时,会触发 register 拦截器。
// 并且接受 tap 作为参数, 还可以对注册事件进行修改;
register: (tapInfo) => {
console.log(`${tapInfo.name} is doing its job`);
return tapInfo
},
// 通过hook实例对象上的call方法时候触发拦截器,接受的参数为调用 Hook 时传入的参数。
call: (arg1, arg2, arg3) => {},
// 在调用被注册的每一个事件函数之前执行,接受参数为对应的 Tap 对象。
tap: (tap) => {},
// loop类型钩子中 每次重新开始 loop 之前会执行该拦截器,拦截器函数接受的参数为调用时传入的参数。
loop: (...args) => {},
});
API
Before && stage
- before 属性的值为数组 | 字符串,值为注册事件对象时的名称,它可以让当前事件在对应标识事件前执行。
- stage 属性的值为数字,数字越大事件回调执行的越晚,支持传入负数,不传默认为0
- 使用:在注册事件函数,第一个参数为对象时,在这个对象添加stage和before来控制本次注册事件的执行时机
- 如果同时使用 before 和 stage 时,会优先处理 before ,在满足 before 的条件之后才会进行 stage 的判断。不建议同时使用
1
2
3
4
5
6
7
8hook.tap({
name: 'flag1'
},()=>{})
hook.tap({
name:'flag2',
// flag2 会在flag1前执行
before:'flag1'
},()=> {})
HookMap
- 辅助类,管理Hook
1
2
3
4
5
6
7
8
9
10
11
12// 创建HookMap实例
const keyedHook = new HookMap((key) => new SyncHook(['arg']));
// 在keyedHook中创建一个name为key1的hook,同时为该hook通过tap注册事件
keyedHook.for('key1').tap('Plugin 1', (arg) => {
console.log('Plugin 1', arg);
});
// 从HookMap中拿到name为key1的hook
const hook = keyedHook.get('key1');
if (hook) {
// 通过call方法触发Hook
hook.call('hello');
} - MultiHook – 不常见
- Context – 即将废弃