webpack从入门到进阶(三)—— webpack核心库tapable使用
前言
tapable是个哈玩意呢?
简单来说,tapabale就是一个类似于EventEmitter的事件驱动,它包含事件监听器和事件触发器。只不过,tapable比EventEmitter更强大,它包含更多的事件类型和事件方法。
那凭啥说tapable是webpack的核心库呢?
webpack进行打包的时候,涉及到很多的 loader 和 plugin,那么webpack和它们之间是如何进行通信的呢?webpack的生命周期钩子(嘿嘿,其实就是webpack打包过程中的各个节点)又是怎么串联起来的呢?其实这一切都是tapable的功劳,这也是为什么说tapable是webpack的核心库。
tapable的使用
SyncHook的使用
- 实例化SyncHook
实例化时接收两个参数,分别是形参的key和hook的名称;事件的执行顺序是同步的。
new SyncHook(['name', 'remark'], 'coffee');
注意,注意,注意,重要的事情说三遍:如果实例化时形参使用了两个参数,那么事件执行的时候接受的时候只有两个参数,即使触发事件的时候传进去的是三个参数。
- 添加事件监听器
tap事件有两个参数,一个是回调事件的名称或者回调事件的配置项,另一个是回调事件。回调事件接收的参数取决于实例化时所传入的形参。
this.hook.coffeeHook.tap('grind', (name) => {
console.log(`grind ${name}`);
});
另外,可以通过 before 和 stage 来调整回调事件的执行顺序。
- before 的权重高于 stage
- stage 使用负值表示执行顺序提前,使用正值表示执行顺序向后
- stage 的绝对值越大,偏移的位置越大
- 触发事件监听器
触发事件监听器时,接收的实参与实例化时注册的形参相对应
this.hook.coffeeHook.call(name, remark);
- 其它同步方法
SyncBailHook:只要当前执行的事件返回值不是undefined,那么后续的事件都不会执行
SyncWaterfallHook:如果当前执行的事件返回的值不为undefined,那么下一个事件的第一个参数替换为该返回值
SyncLoopHook:当前执行的事件回调的返回值不是
undefined
时,会重新从第一个注册的事件回调处执行,直到当前执行的事件回调没有返回值
AsyncParallelHook的使用
- 实例化AsyncParallelHook
实例化时接收两个参数,分别是形参的key和hook的名称;事件的执行顺序是并行的。
new AsyncParallelHook(['name', 'remark'], 'coffee');
- 添加事件监听器
AsyncParallelHook支持通过 tap、tapAsync 和 tapPromise 三个方法注册监听事件;tap方法与SyncHook的相同,tapAsync我们先留个悬念,放到下面讲,我们这里只介绍一下tapPromise。
tapPromise事件有两个参数,一个是回调事件的名称或者回调事件的配置项,另一个是回调事件。回调事件接收的参数取决于实例化时所传入的形参。
this.hook.coffeeHook.tapPromise({
name: 'brew',
}, (name, remark) => {
console.log(`brew ${name}, ${remark}`);
return Promise.reject('brew fail');
});
tapPromise回调事件必须返回Promise,否则会报错Error: Tap function (tapPromise) did not return promise (returned undefined)
- 触发回调事件
触发事件监听器时,接收的实参与实例化时注册的形参相对应;
- 通过callAsync触发
this.hook.coffeeHook.callAsync(name, remark);
- 通过promise触发
this.hook.coffeeHook
.promise(name, remark)
.then((data) => {
console.log('data::::::::', data);
})
.catch(err => {
console.log('err::::::::', err);
});
- 验证toPromise是并行的
在前一个回调事件中添加一个定时器,如果是顺序执行,那么前一个resolve之前,后一个回调事件不会执行的;反之,如果后一个回调事件先执行了,那说明toPromise就是并行的。
通过下述代码,得出结论:toPromise是并行的
this.hook.coffeeHook.tapPromise({
name: 'boil',
}, (...agus) => {
console.log('boil water');
console.log('boil agus:::::::', agus);
return new Promise((resolve) => {
// 如果是同步执行的
setTimeout(() => {
console.log('boil success');
resolve('success');
}, 2000);
});
});
this.hook.coffeeHook.tapPromise({
name: 'brew',
}, (name, remark) => {
console.log(`brew ${name}, ${remark}`);
return Promise.resolve('brew success');
});
// boil water
// boil agus::::::: [ 'Blue Mountain Coffee', 'use suga' ]
// brew Blue Mountain Coffee, use suga
// garland
// garland agus::::::: [ 'Blue Mountain Coffee', 'use suga' ]
// boil success
- 其它同步方法
AsyncParallelBailHook:只要当前执行的事件返回值不是undefined,那么后续的事件都不会执行
AsyncSeriesHook的使用
- 实例化AsyncSeriesHook
实例化时接收两个参数,分别是形参的key和hook的名称;事件的执行顺序是顺序的。
new AsyncParallelHook(['name', 'remark'], 'coffee');
- 添加事件监听器
AsyncParallelHook支持通过 tap、tapAsync 和 tapPromise 三个方法注册监听事件;tap方法与tapPromise方法已经介绍过了,这里我们再介绍一下tapAsync。
tapAsync事件有两个参数,一个是回调事件的名称或者回调事件的配置项,另一个是回调事件。回调事件接收的参数取决于实例化时所传入的形参。
this.hook.coffeeHook.tapAsync({
name: 'brew',
}, async (name, callback) => {
console.log(`brew ${name}`);
await new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('brew success');
} else {
reject('brew fail');
}
}, 2000);
})
.then(result => {
console.log('brew result::::::', result);
callback(undefined, result);
})
.catch(err => {
callback(err);
});
});
- 回调事件接收一个callback
- 如果不执行callback,那么后续的回调事件就不会执行
- 如果执行callback,并且传入err,那么就会直接执行callback,并且后续回调事件不会执行
- 如果执行callback,没有传入err,那么回调事件就会就行执行,直到结束才执行callback方法
- 触发回调事件
触发事件监听器时,接收的实参与实例化时注册的形参相对应,并且在最后传入一个callback事件;
- 通过callAsync触发
this.hook.coffeeHook.callAsync(name, remark, (err, data) => {
if (err) {
console.log('err::::::::', err);
} else {
console.log('data::::::::', data);
}
});
- 通过promise触发
this.hook.coffeeHook
.promise(name, remark)
.then((data) => {
console.log('data::::::::', data);
})
.catch(err => {
console.log('err::::::::', err);
});
- 其它同步方法
AsyncSeriesBailHook:只要当前执行的事件返回值不是undefined,那么后续的事件都不会执行
AsyncSeriesWaterfallHook:如果当前执行的事件返回的值不为undefined,那么下一个事件的第一个参数替换为该返回值
EventEmitter
提供一下EventEmitter实现的事件驱动的代码,仅供了解。
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('coffee', function grindListener(name) {
console.log(`grind ${name}`);
});
// 第二个监听器
emitter.on('coffee', function secondListener(name, remark) {
console.log(`grind ${name}, ${remark}`);
});
// 第三个监听器
emitter.on('coffee', function garlandListener() {
console.log(`garland`);
});
emitter.emit('coffee', 'Blue Mountain Coffee', 'use suga');
转载自:https://juejin.cn/post/7045953287740194830