了解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』。
- 注册事件: 通过tap函数监听对应的事件,接受两个参数:参数一仅作标识, 参数二在调用时会执行
1
hook.tap(String|Object, 本次注册的函数)
- 调用事件: 通过call方法传入对应的参数,调用注册在hook内部的事件函数执行,同时将call方法传入的参数传递给每一个注册的事件函数作为『实参』
- 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14const {SyncHook, SyncBailHook, SyncWaterfallHook,SyncWaterfallHook } = require('tapable')
const hook = new SyncHook(['arg1','arg2','arg3']) // 这个就换钩子名,其他一样
// SyncHook注册与调用事件
// 初始化同步钩子
重点:数组中对应的字符串个数
hook.tap('flag1',(arg1,arg2,arg3) => {})
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 值类型为Array | String,值为注册事件对象时的名称,它可以让当前事件在对应标识事件前执行』
- stage 值类型为Number,『数字越大事件回调执行的越晚』,支持传入负数,默认为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');
}