tapable是什么
tapable是webpack的核心模块,是一个增强版的发布订阅库,是webpack plugin的基本实现方式。为使用者提供强大的hook机制。
tapable的主要api
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
tapable的分类
1.根据hook的类型可以分为同步Sync和异步Async,异步又可以分为并行和串行
- Tapable
- 同步Hook
SyncHook
SyncBailHook
SyncWaterfallHook
SyncLoopHook - 异步钩子
- 异步并行
AsyncParallelHook
AsyncParallelBailHook - 异步串行
AsyncSeriesHook
AsyncSeriesBailHook
AsyncSeriesWaterHook
- 异步并行
- 同步Hook
2.hook的分类-按结果分
- Tapable
- Basic 按顺序执行每个事件函数,不关心函数的返回值
SyncHook
AsyncParallelHook
AsyncSeriesHook - Bail 执行每个事件函数,遇到结果不为undefined则结束执行,直接走call的回调
SyncBailHook
AsyncParallelBailHook
AsyncSeriesBailHook - Waterfall 将最近上一个函数的返回值作为下一个函数的参数,如果上一个函数没有返回值(返回undefined),那么就继续找上上个,如果找不到就用自己传入的参数
SyncWaterfallHook
AsyncSeriesWaterfallHook - Loop 不停的循环执行事件函数,直到所有函数结果 result === undefined,每次循环都是从头开始的SyncLoopHook
- Basic 按顺序执行每个事件函数,不关心函数的返回值
用法
注册事件回调
注册事件回调有三个方法: tap、tapAsync 和 tapPromise,其中 tapAsync 和 tapPromise 不能用于 Sync 开头的钩子类
触发事件
触发事件的三个方法是与注册事件回调的方法一一对应的,call 对应 tap、callAsync 对应 tapAsync 和 promise 对应 tapPromise。
SyncHook
//按顺序执行每个事件函数,不关心函数的返回值
const { SyncHook } = require('tapable');
const hook = new SyncHook();
hook.tap('first', () => {
console.log('first');
});
hook.tap(
'second',
() => {
console.log('second');
}
);
hook.call('call');
/**
* output:
*
* first
* second
*/
AsyncParallelHook
const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);
hook.tapAsync('first', (name, callback) => {
console.log('first', name, callback);
// 必须执行否则不会执行后续的回调 继续调用second
callback();
});
hook.tapAsync('second', (name, callback) => {
console.log('second', name, callback);
// 执行 callAsync 传入的回调
callback(name);
});
// callAsync函数的params 对应tapAsync函数的第二个参数 回调函数的name
hook.callAsync('params', (error) => {
console.log('callAsync', error);
});
/**
* output:
*
* second params [Function (anonymous)]
* callAsync params
* first params [Function (anonymous)]
*/
AsyncSeriesHook
const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['name']);
hook.tapAsync('first', (name, callback) => {
console.log('first', name, callback);
// 必须执行否则不会执行后续的回调 继续调用second
callback();
});
hook.tapAsync('second', (name, callback) => {
console.log('second', name, callback);
// 执行 callAsync 传入的回调
callback(name);
});
// callAsync函数的params 对应tapAsync函数的第二个参数 回调函数的name
hook.callAsync('params', (error) => {
console.log('callAsync', error);
});
/**
* Console output:
*
* first params [Function]
* second params [Function]
* callAsync undefined
*/
/**---------------------------------*/
const { AsyncSeriesHook } = require('tapable');
const queue = new AsyncSeriesHook(['name']);
queue.tapPromise("1", function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(1, name);
resolve();
}, 1000);
});
});
queue.tapPromise("2", function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(2, name);
reject('e');
}, 2000);
});
});
queue.tapPromise("3", function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(3, name);
resolve();
}, 3000);
});
});
queue.promise("yh").then((data) => {
console.log(data);
}, (err) => {
console.log(err);
});
/**
* Console output:
*
* 1 yh
* 2 yh
* e
*/
SyncBailHook
只要回调函数中有一个返回不为undefined的就调过剩余的回到函数
const { SyncBailHook } = require('tapable')
const 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 '2'
})
syncBailHook.tap('3', (name, age) => {
console.log(3, name, age)
})
syncBailHook.call('yh', 18)
/**
* Console output:
* 1 yh 18
* 2 yh 18
*/
// 假如把2换为 下面的例子
syncBailHook.tap('2', (name, age) => {
console.log(2, name, age)
return null
})
/**
* Console output:
* 1 yh 18
* 2 yh 18
*/
AsyncParallelBailHook
const { AsyncParallelBailHook } = require('tapable')
const queue = new AsyncParallelBailHook(['name']);
queue.tapAsync("1", function (name, callback) {
console.log(1);
callback();
});
queue.tapAsync("2", function (name, callback) {
console.log(2);
callback('s');
});
queue.tapAsync("3", function (name, callback) {
console.log(3);
callback();
});
queue.callAsync("yh", (err) => {
console.log('call',err);
});
/**
* 1
* 2
* call s
*/
AsyncSeriesBailHook
queue.tapAsync("1", function (name, callback) {
setTimeout(function () {
console.log(1);
callback("wrong");
}, 1000);
});
queue.tapAsync("2", function (name, callback) {
setTimeout(function () {
console.log(2);
callback();
}, 2000);
});
queue.tapAsync("3", function (name, callback) {
setTimeout(function () {
console.log(3);
callback();
}, 3000);
});
queue.callAsync("yh", (err) => {
console.log(err);
});
/**
* 1
* wrong
*/
SyncWaterfallHook
const { SyncWaterfallHook } = require('tapable')
const syncWaterfallHook = new SyncWaterfallHook(['name', 'age']);
syncWaterfallHook.tap('1', (name, age) => {
console.log(1, name, age)
return 'test1'
})
syncWaterfallHook.tap('2', (name, age) => {
console.log(2, name, age)
return 'test2'
})
syncWaterfallHook.tap('3', (name, age) => {
console.log(3, name, age)
return 'test3'
})
syncWaterfallHook.tap('4', (name, age) => {
console.log(4, name, age)
})
syncWaterfallHook.call('yh', 18)
/**
* output
* 1 yh 18
* 2 test1 18
* 3 test2 18
* 4 test3 18
*/
AsyncSeriesWaterfallHook
let { AsyncSeriesWaterfallHook } = require("tapable");
let queue = new AsyncSeriesWaterfallHook(["name", "age"]);
queue.tapAsync("1", function (name, age, callback) {
setTimeout(function () {
console.log(1, name, age);
callback(null, 1);
}, 1000);
});
queue.tapAsync("2", function (data, age, callback) {
setTimeout(function () {
console.log(2, data, age);
callback('test2', 2);
}, 2000);
});
queue.tapAsync("3", function (data, age, callback) {
setTimeout(function () {
console.log(3, data, age);
callback(null, 3);
}, 3000);
});
queue.callAsync("yh", 10, (err, data) => {
console.log(err, data);
});
/**
* 1 yh 10
* 2 1 10
* test2 undefined
*/
SyncLoopHook
let { SyncLoopHook } = require("tapable");
const syncLoopHook = new SyncLoopHook(['name', 'age']);
let counter1 = 0;
let counter2 = 0;
syncLoopHook.tap('1', (name, age) => {
console.log(1, 'counter1', counter1, name, age);
if (++counter1 < 5) {
return 1;
}
})
syncLoopHook.tap('2', (name, age) => {
console.log(2, 'counter2', counter2, name, age);
})
syncLoopHook.call('yh', 18);
// output
// 1 counter1 0 yh 18
// 1 counter1 1 yh 18
// 1 counter1 2 yh 18
// 1 counter1 3 yh 18
// 1 counter1 4 yh 18
// 2 counter2 0 yh 18
// 源码拼接出来的可执行代码
// do {
// _loop = false;
// var _fn0 = _x[0];
// var _result0 = _fn0(name, age);
// if (_result0 !== undefined) {
// _loop = true;
// } else {
// var _fn1 = _x[1];
// var _result1 = _fn1(name, age);
// if (_result1 !== undefined) {
// _loop = true;
// } else {
// if (!_loop) {}
// }
// }
// } while (_loop);
参考
https://blog.csdn.net/qq_17175013/article/details/119547711
https://zhuanlan.zhihu.com/p/100974318
https://www.infoq.cn/article/lcpop63kdeosott5fvjx