Tapable是什么
- 官网的解释:是 webpack 的一个核心工具,但也可用于其他地方, 以提供类似的插件接口。 在
Webpack
中的许多对象都扩展自Tapable
类。 它对外暴露了tap
,tapAsync
和tapPromise
等方法, 插件可以使用这些方法向Webpack
中注入自定义构建的步骤,这些步骤将在构建过程中触发。 Webpack
本质上是基于事件流的。它的工作流程就是将各个插件串联起来,而实现这一切的核心就是tapable,核心原理是依赖于发布订阅模式(统一由调度中心进行处理,订阅者和发布者互不干扰)。Webpack
的生命周期是通过Tapable
提供的钩子类来实现的。- tapable 注册函数 的方法有三种:
tap
(同步)、tapAsync
(异步)、tapPromise
(异步)。 - 相对应的 执行方法 有三种:
call
(同步)、callAsync
(异步)、promise
(异步)。
注册/执行方法
用于同步、异步钩子
用于异步钩子,用 tabAsync
注册的事件处理函数最后一个参数都为一个回调函数 callback
,每个事件处理函数在异步代码执行完毕后调用 callback
函数,则可以保证 callAsync
会在所有异步函数都执行完毕后执行。
用于异步钩子,用 tapPromise
注册事件处理函数,必须返回一个 Promise
实例,而 promise
方法也返回一个 Promise
实例,并将返回值传入 resolve
方法
Tapable钩子
按照命名可以将钩子分为这几类:
-
waterfallHook:将上一个监听的执行结果当作下一个监听的入参
-
bailHook:执行到返回结果为非undefined的监听时停止监听
-
seriesHook:串行执行异步监听
-
parallelHook:并行执行异步监听
-
SyncHook
同步串行钩子,不关心事件处理函数的返回值,在触发事件时之后,会按照事件注册的先后顺序执行所有的事件处理函数。
const { SyncHook } = require("tapable");
// 创建实例
let syncHook = new SyncHook(["name", "age"]);
// 注册事件
syncHook.tap("1", (name, age) => console.log("No.1", name, age));
syncHook.tap("2", (name, age) => console.log("No.2", name, age));
syncHook.tap("3", (name, age) => console.log("No.3", name, age));
// 触发事件,让监听函数执行
syncHook.call("cookie", 8); // cookie是我的小猫,嘿嘿,8个月大啦
// 输出结果
// No.1 cookie 8
// No.2 cookie 8
// No.3 cookie 8
简单实现原理:
class SyncHook {
constructor() {
this.hooks = [];
}
// 定义注册事件
tap(name, fn) {
this.hooks.push(fn);
}
// 定义触发事件(按注册事件的先后顺序执行)
call(...args) {
this.hooks.forEach((hook) => hook(...args));
}
}
同步串行熔断保险钩子,与 SyncHook
相似,但当返回的内容为非undefined时,会停止执行监听函数
const { SyncBailHook } = require("tapable");
// 创建实例
let syncBailHook = new SyncBailHook(["name", "age"]);
// 注册事件
syncBailHook.tap("1", (name, age) => console.log("No.1", name, age));
syncBailHook.tap("2", (name, age) => {
console.log("No.2", name, age);
return null
});
syncBailHook.tap("3", (name, age) => console.log("No.3", name, age));
// 触发事件,让监听函数执行
syncBailHook.call("cookie", 8);
// 输出结果
// No.1 cookie 8
// No.2 cookie 8
简单实现原理:
class SyncBailHook {
constructor(args){
this.hooks = [];
}
// 定义注册事件
tap(name, fn){
this.hooks.push(fn);
}
// 定义触发事件
call(...args){
let result = undefined; // 当前函数的返回值
let index = 0;// 当前是第几个函数
do{
result = this.hooks[index](...args);
index++;
}while(result === undefined && this.hooks.length > index);
}
}
同步串行瀑布钩子,前一个的返回值作为后一个的入参,需要考虑执行的顺序,需要且只能传一个形参
const { SyncWaterfallHook } = require("tapable");
// 创建实例
let syncWaterfallHook = new SyncWaterfallHook(["name"]);
// 注册事件
syncWaterfallHook.tap("1", (name) => {
console.log("No.1", name);
return "north"; // north也是我的小猫
});
syncWaterfallHook.tap("2", (name) => {
console.log("No.2", name);
return "south"
});
syncWaterfallHook.tap("3", (name) => {
console.log("No.3", name)
});
// 触发事件,让监听函数执行
syncWaterfallHook.call("cookie");
// 输出结果
// No.1 cookie
// No.2 north
// No.3 south
简单实现原理:
class SyncBailHook {
constructor(args){
this.hooks = [];
}
// 定义注册事件
tap(name, fn){
this.hooks.push(fn);
}
// 定义触发事件
call(){
let result; // 当前函数的返回值
this.hooks.forEach((hook, index) => {
// 当执行第一个函数时,将call传入的参数作为下一个函数的入参,之后的函数将上一个函数的返回值作为下一个函数的入参
result = index === 0 ? hook(...arguments) : hook(result);
});
}
}
同步串行循环钩子,当遇到某个函数返回非undefined的函数时重复循环
const { SyncLoopHook } = require("tapable");
// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);
// 定义辅助变量
let total = 0;
// 注册事件
syncLoopHook.tap("1", (name, age) => {
console.log("No.1", name, age, total);
return total++ <= 2 ? "继续循环" : undefined
});
syncLoopHook.tap("2", (name, age) => console.log("No.2", name, age, total));
// 触发事件,让监听函数执行
syncLoopHook.call("cookie", 8);
// 输出结果
// No.1 cookie 8 0
// No.1 cookie 8 1
// No.1 cookie 8 2
// No.2 cookie 8 3
简单实现原理:参考https://juejin.cn/post/6844903812705026055
注意:同步的钩子必须用tap注册
异步并联钩子,callback
方法是为了检测是否已经满足条件执行 callAsync
的回调,如果中间某个事件处理函数没有调用 callback
,只是不会调用 callAsync
的回调,但是所有的事件处理函数都执行了。
const { AsyncParallelHook } = require("tapable");
// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
// 注册事件
asyncParallelHook.tapAsync("1", (name, age, callback) => {
setTimeout(() => {
console.log("No.1", name, age);
callback();
}, 1000)
});
asyncParallelHook.tapAsync("2", (name, age, callback) => {
setTimeout(() => {
console.log("No.2", name, age);
callback();
}, 2000)
});
// 触发事件,让监听函数执行
asyncParallelHook.callAsync("cookie", 8, () => console.log("异步结束"));
// 输出结果
// No.1 cookie 8
// No.2 cookie 8
// 异步结束
异步并行,上例中的两个定时器最长时长为2s,且两个函数是并行的,这个例子一共执行了2s左右
异步并联保险钩子,当返回的内容为非undefined时,会停止执行监听函数
异步串联钩子,在注册事件的回调中如果不调用 callback
,则在触发事件时会在没有调用 callback
的事件处理函数的位置 “卡死”,即不会继续执行后面的事件处理函数,只有都调用 callback
才能继续,而最后一个事件处理函数中调用 callback
决定是否调用 callAsync
的回调。
异步串联,结合AsyncParallelHook
的例子,两个函数是串行的,所以一共执行了1s+2s=3s
异步串联保险钩子,当返回的内容为非undefined时,会停止执行监听函数
异步串联瀑布钩子,前一个的返回值作为后一个的入参,需要且只能传一个形参
未完待续~~
剩余部分:tapable的应用场景等