Tapable
tapable包公开了许多Hook类,这些类可用于为插件创建钩子。
const {
AsyncParallelBailHook,
AsyncParallelHook,
AsyncSeriesBailHook,
AsyncSeriesHook,
AsyncSeriesWaterfallHook,
SyncBailHook,
SyncHook,
SyncLoopHook,
SyncWaterfallHook
} = require("tapable");
安装
npm install --save tapable
用法
所有Hook构造函数都有一个可选参数,它是一个字符串形式的参数名称列表。
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
最佳实践是在类中公开类的所有钩子 hooks 属性:
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
brake: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
};
}
/* ... */
}
其他人现在可以使用这些钩子:
const myCar = new Car();
// Use the tap method to add a consumer (plugin)
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
它需要传递一个名称来标识插件/原因。
您可能会收到以下参数:
myCar.hooks.accelerate.tap("LoggerPlugin", (newSpeed) =>
console.log(`Accelerating to ${newSpeed}`)
);
对于同步钩子, tap 是添加插件的唯一有效方法。异步钩子也支持异步插件:
myCar.hooks.calculateRoutes.tapPromise(
"GoogleMapsPlugin",
(source, target, routesList) =>
// return a promise
google.maps.findRoute(source, target).then((route) => {
routesList.add(route);
})
);
myCar.hooks.calculateRoutes.tapAsync(
"BingMapsPlugin",
(source, target, routesList, callback) => {
bing.findRoute(source, target, (err, route) => {
if (err) return callback(err);
routesList.add(route);
// call the callback
callback();
});
}
);
// You can still use sync plugins
myCar.hooks.calculateRoutes.tap(
"CachedRoutesPlugin",
(source, target, routesList) => {
const cachedRoute = cache.get(source, target);
if (cachedRoute) routesList.add(cachedRoute);
}
);
声明这些钩子的类需要调用它们:
class Car {
/**
* You won't get returned value from SyncHook or AsyncParallelHook,
* to do that, use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively
*/
setSpeed(newSpeed) {
// following call returns undefined even when you returned values
this.hooks.accelerate.call(newSpeed);
}
useNavigationSystemPromise(source, target) {
const routesList = new List();
return this.hooks.calculateRoutes
.promise(source, target, routesList)
.then((res) =>
// res is undefined for AsyncParallelHook
routesList.getRoutes()
);
}
useNavigationSystemAsync(source, target, callback) {
const routesList = new List();
this.hooks.calculateRoutes.callAsync(source, target, routesList, (err) => {
if (err) return callback(err);
callback(null, routesList.getRoutes());
});
}
}
钩子将编译一个方法,以最有效的方式运行你的插件。它生成的代码取决于:
- 已注册插件的数量(无,一个,多个)
- 注册的插件类型(sync, async, promise)
- 使用的调用方法(sync, async, promise)
- 参数的数量
- 是否使用拦截
这确保了最快的执行速度。看到 代码生成 有关运行时编译的详细信息。
插件API
在钩子上注册一个回调函数 tap* 方法。钩子类型决定哪些是有效的(参见 钩类):
hook.tap(nameOrOptions, fn)-注册一个同步回调。hook.tapAsync(nameOrOptions, fn)-注册一个基于回调的异步回调。传递给的最后一个参数fn是节点风格的回调吗(err, result)。hook.tapPromise(nameOrOptions, fn)-注册一个返回承诺的异步回调。如果fn返回一些不可用的东西,钩子抛出。
第一个参数可以是一个字符串(插件名)或一个选项对象,也允许影响点击运行的顺序:
hook.tap(
{
name: "MyPlugin",
stage: -10, // lower stages run earlier, default is 0
before: "OtherPlugin" // run before a named tap (string or string[])
},
(...args) => {
/* ... */
}
);
| 选项 | 类型 | 描述 |
|---|---|---|
name | string | 必需的。标识用于调试、拦截器和 before 选择。 |
stage | number | 默认为 0. 低级水龙头先于高级水龙头运行。具有相同阶段的抽头按注册顺序运行。 |
before | string | string[] | 该抽头插入在指定抽头之前。未知的名字将被忽略。结合 stage, before 获胜是因为它的目标用户;其他水龙头仍按 stage。 |
这个 name 也被一些生态系统(如webpack)用于分析和错误消息。在单个监听注册中,以后的拦截器 register 钩子仍然可以替换tap对象(参见 拦截).
hook.withOptions(options)
withOptions 返回钩子周围的facade tap* 方法自动合并 options 进入每一个注册。对于想要预配置 stage 或 before 对于他们添加的所有水龙头:
const lateHook = myCar.hooks.accelerate.withOptions({ stage: 10 });
lateHook.tap("LogAfterOthers", (speed) => console.log("final speed", speed));
// equivalent to: myCar.hooks.accelerate.tap({ name: "LogAfterOthers", stage: 10 }, ...)
返回的对象不公开 call* 方法,因此分发给插件是安全的。
一个可运行的示例显示如何 withOptions 影响水龙头排序:
const { SyncHook } = require("tapable");
const hook = new SyncHook(["value"]);
hook.tap("Default", (v) => console.log("default", v));
// Pre-configure stage: 10 so all taps registered through `late` run last.
const late = hook.withOptions({ stage: 10 });
late.tap("RunLast", (v) => console.log("last", v));
// Pre-configure stage: -10 so these taps run first. Each facade can also
// be further narrowed via `withOptions`.
const early = hook.withOptions({ stage: -10 });
early.tap("RunFirst", (v) => console.log("first", v));
hook.call(1);
// first 1
// default 1
// last 1
的值 withOptions. 例如, late.tap({ name: "Override", stage: 0 }, fn) 忽略表面的 stage: 10 和寄存器 fn 在阶段 0。
钩类型
每个钩子都可以有一个或几个功能。它们的执行方式取决于钩子类型:
-
基本钩(没有“瀑布”,“保释”或“循环”的名称)。这个钩子只是调用它在一行中点击的每个函数。
-
瀑布. 瀑布钩子也会调用一行中的每个被选中的函数。与基本钩子不同,它将每个函数的返回值传递给下一个函数。
-
保释. 保释挂钩允许提前退出。当任何选中的函数返回任何结果时,保释钩子将停止执行剩余的函数。
-
循环. 当一个在循环钩子中的插件返回一个未定义的值时,钩子将从第一个插件重新启动。它将循环直到所有插件返回undefined。
此外,钩子可以是同步的,也可以是异步的。为了反映这一点,有“Sync”,“AsyncSeries”和“AsyncParallel”钩子类:
-
同步. 同步钩子只能通过同步函数调用(使用
myHook.tap()). -
AsyncSeries. 异步系列钩子可以使用同步的、基于回调的和基于承诺的函数(使用
myHook.tap(),myHook.tapAsync()和myHook.tapPromise()). 它们在一行中调用每个异步方法。 -
AsyncParallel. 异步并行钩子也可以使用同步的、基于回调的和基于承诺的函数(使用
myHook.tap(),myHook.tapAsync()和myHook.tapPromise()). 但是,它们并行地运行每个异步方法。
钩子类型反映在它的类名中。例如, AsyncSeriesWaterfallHook 允许异步函数并串联运行它们,将每个函数的返回值传递给下一个函数。
钩类
下表总结了9个内置钩子类。对于每个类:
- 利用方法 是
tapX可用于注册处理程序的变体。 - 调用方法 是钩子的所有者触发它的方式。
- 结果 返回值吗
call(或传递给callAsync的回调/解析promise调用)。 - 从抽头返回值 描述从抽头函数返回的值是否有效果。
| 类 | 利用方法 | 调用方法 | 结果 | 从抽头返回值 |
|---|---|---|---|---|
SyncHook | tap | call | undefined | 忽略了 |
SyncBailHook | tap | call | 第一个非undefined 价值,或者 undefined | 使钩子短路 |
SyncWaterfallHook | tap | call | 最终值(最后一次点击后的第一个参数) | 作为第一个参数传递给下一个点击 |
SyncLoopHook | tap | call | undefined | 非undefined 从第一次点击开始重新启动循环 |
AsyncParallelHook | tap, tapAsync, tapPromise | callAsync, promise | undefined | 忽略了 |
AsyncParallelBailHook | tap, tapAsync, tapPromise | callAsync, promise | 第一个非undefined 价值,或者 undefined | 使钩子短路 |
AsyncSeriesHook | tap, tapAsync, tapPromise | callAsync, promise | undefined | 忽略了 |
AsyncSeriesBailHook | tap, tapAsync, tapPromise | callAsync, promise | 第一个非undefined 价值,或者 undefined | 使钩子短路 |
AsyncSeriesLoopHook | tap, tapAsync, tapPromise | callAsync, promise | undefined | 非undefined 从第一次点击开始重新启动循环 |
AsyncSeriesWaterfallHook | tap, tapAsync, tapPromise | callAsync, promise | 最终值(最后一次点击后的第一个参数) | 作为第一个参数传递给下一个点击 |
每个类的详细行为:
SyncHook
一个基本的同步钩子。每个被选中的函数都是按注册顺序调用的,并带有传递给它的参数 call. 调用函数的返回值将被忽略 call 返回 undefined。
- 利用方法:
tap - 调用方法:
call tapAsync和tapPromise抛出错误。
const hook = new SyncHook(["name"]);
hook.tap("A", (name) => console.log(`hello ${name}`));
hook.tap("B", (name) => console.log(`hi ${name}`));
hook.call("world");
// hello world
// hi world
SyncBailHook
允许提前退出的同步钩子。每个选中的函数都按顺序调用,直到返回一个非undefined 价值;这个值变成的结果 call 剩下的点击会被跳过。如果所有轻击都返回 undefined, call 返回 undefined。
- 利用方法:
tap - 调用方法:
call
const hook = new SyncBailHook(["value"]);
hook.tap("Negative", (v) => (v < 0 ? "negative" : undefined));
hook.tap("Zero", (v) => (v === 0 ? "zero" : undefined));
hook.tap("Positive", (v) => "positive");
hook.call(-1); // "negative" (later taps skipped)
hook.call(5); // "positive"
SyncWaterfallHook
一种同步钩子,通过其抽头函数来执行一个值。传递给的第一个参数 call 被转发到第一次点击。如果轻击返回非-undefined 值,它将为下一次点击替换该参数;否则,保留之前的值。 call 返回最后一个抽头运行后的值。额外的参数(如果有的话)将不加更改地传递。
- 利用方法:
tap - 调用方法:
call
const hook = new SyncWaterfallHook(["value"]);
hook.tap("Double", (v) => v * 2);
hook.tap("PlusOne", (v) => v + 1);
hook.call(3); // 7 -> (3 * 2) + 1
SyncLoopHook
一种同步钩子,它不停地重新运行它的水龙头,直到所有水龙头都返回为止 undefined 要一张完整的通行证。每当轻击返回非-时undefined 钩子从第一次点击开始重新启动。 call 返回 undefined。
- 利用方法:
tap - 调用方法:
call
const hook = new SyncLoopHook(["state"]);
let retries = 3;
hook.tap("Retry", () => {
if (retries-- > 0) return true; // non-undefined restarts the loop
});
hook.tap("Log", () => console.log("pass"));
hook.call({});
// pass (runs once all taps return undefined)
AsyncParallelHook
一个异步钩子,并行运行它所有的调用函数。当每个tap都发出完成信号(同步返回、回调或承诺解决)时,它完成。返回值和解析值被忽略; callAsync的回调被调用时没有结果,并且 promise() 解析为 undefined. 如果有任何抽头错误,错误将被转发,剩余的抽头仍然完成,但其结果将被丢弃。
- 利用方法:
tap,tapAsync,tapPromise - 调用方法:
callAsync,promise
const hook = new AsyncParallelHook(["source"]);
hook.tapPromise("Fetch", (src) => fetch(src));
hook.tapAsync("Log", (src, cb) => {
console.log("fetching", src);
cb();
});
await hook.promise("https://example.com");
AsyncParallelBailHook
就像 AsyncParallelHook,但其目的是为了摆脱困境。所有抽头函数并行启动;第一个水龙头产生一个非undefined 值(同步的,通过它的回调,或者通过解析它的promise)决定钩子的结果。剩下的水龙头继续运行,但它们的结果被忽略。顺序由轻击注册顺序决定:较早轻击的值优先于较晚轻击的值,即使后一个轻击先完成。
- 利用方法:
tap,tapAsync,tapPromise - 调用方法:
callAsync,promise
const hook = new AsyncParallelBailHook(["key"]);
hook.tapPromise("Cache", async (key) => cache.get(key));
hook.tapPromise("Db", async (key) => db.lookup(key));
const value = await hook.promise("user:42");
// First non-undefined result (by registration order) wins.
AsyncSeriesHook
一种异步钩子,它一个接一个地运行选定的函数,等待每个函数完成后再开始下一个函数。结果被忽略; callAsync的回调被调用时没有结果,并且 promise() 解析为 undefined. 第一个错误将终止该系列。
- 利用方法:
tap,tapAsync,tapPromise - 调用方法:
callAsync,promise
const hook = new AsyncSeriesHook(["request"]);
hook.tapPromise("Authenticate", async (req) => authenticate(req));
hook.tapPromise("Log", async (req) => logger.info(req.url));
await hook.promise(request);
AsyncSeriesBailHook
允许提前退出的异步系列钩子。选中的函数一个接一个地运行;一旦产生非-undefined 值,则该值成为钩子的结果,并跳过剩余的点击。
- 利用方法:
tap,tapAsync,tapPromise - 调用方法:
callAsync,promise
const hook = new AsyncSeriesBailHook(["id"]);
hook.tapPromise("Memory", async (id) => memory.get(id));
hook.tapPromise("Disk", async (id) => disk.read(id));
const value = await hook.promise("doc-1");
// Stops at the first tap that produces a value.
AsyncSeriesLoopHook
循环的异步系列钩子。选中的函数一个接一个地运行;每当轻击产生非-时undefined 钩子从第一次点击开始重新启动。当一个完整的传递产生时,钩子完成 undefined 从每一个水龙头。结果总是 undefined。
- 利用方法:
tap,tapAsync,tapPromise - 调用方法:
callAsync,promise
const hook = new AsyncSeriesLoopHook(["job"]);
hook.tapPromise("Process", async (job) => {
const more = await job.step();
if (more) return true; // restart the loop
});
await hook.promise(job);
AsyncSeriesWaterfallHook
一个异步系列钩子,通过它的抽头执行一个值。传递给的第一个参数 callAsync / promise 被转发到第一次点击。水龙头不是undefined 返回/回调/解析值为下一次点击替换它; undefined 保持先前的值。钩子在最后一次点击后完成该值。
- 利用方法:
tap,tapAsync,tapPromise - 调用方法:
callAsync,promise
const hook = new AsyncSeriesWaterfallHook(["source"]);
hook.tapPromise("Read", async (src) => fs.readFile(src, "utf8"));
hook.tapPromise("Trim", async (text) => text.trim());
const output = await hook.promise("./input.txt");
拦截
所有钩子都公开一个 intercept(interceptor) 方法。拦截器是一个普通对象,它的方法在钩子生命周期中的特定点被调用。拦截器在监听之前按注册顺序调用,对于记录、跟踪、分析或重新映射监听选项很有用。
myCar.hooks.calculateRoutes.intercept({
name: "LoggingInterceptor",
call: (source, target, routesList) => {
console.log("Starting to calculate routes");
},
tap: (tapInfo) => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ..., stage: 0 }
console.log(`${tapInfo.name} is running`);
},
register: (tapInfo) => {
// Called once per tap (and for each tap already registered when the
// interceptor is added). Return a new tapInfo object to replace it.
console.log(`${tapInfo.name} is registered`);
return tapInfo;
}
});
| 处理程序 | 签名 | 当它运行时 |
|---|---|---|
call | (...args) => void | 在钩子开始执行它的水龙头之前。接收传递给的参数 call / callAsync / promise。 |
tap | (tap: Tap) => void | 在每个水龙头运行之前。这个 tap 对象是快照-忽略突变。 |
loop | (...args) => void | 在a的每次迭代开始时 SyncLoopHook / AsyncSeriesLoopHook。 |
error | (err: Error) => void | 每当tap抛出、拒绝或调用回调时出现错误。 |
result | (result: any) => void | 当保释或瀑布钩子产生一个值时,或者当tap为循环钩子产生一个值时。 |
done | () => void | 当钩子成功完成时(没有错误,没有提前保释)。 |
register | (tap: Tap) => Tap | undefined | 在注册时每次监听一次(包括在添加拦截器之前存在的监听)。返回new Tap 对象来替换它。 |
name | string | 生态系统调试时使用的可选标签。 |
context | boolean | 选择加入共享的 context 对象。看到 上下文。 |
添加拦截器会使钩子的编译调用函数失效 call / callAsync / promise 重新编译它,以便将新的拦截器编入其中。
上下文
插件和拦截器可以选择访问一个可选的 context 对象,该对象可用于将任意值传递给后续的插件和拦截器。
myCar.hooks.accelerate.intercept({
context: true,
tap: (context, tapInfo) => {
// tapInfo = { type: "sync", name: "NoisePlugin", fn: ... }
console.log(`${tapInfo.name} is doing it's job`);
// `context` starts as an empty object if at least one plugin uses `context: true`.
// If no plugins use `context: true`, then `context` is undefined.
if (context) {
// Arbitrary properties can be added to `context`, which plugins can then access.
context.hasMuffler = true;
}
}
});
myCar.hooks.accelerate.tap(
{
name: "NoisePlugin",
context: true
},
(context, newSpeed) => {
if (context && context.hasMuffler) {
console.log("Silence...");
} else {
console.log("Vroom!");
}
}
);
HookMap
一个 HookMap 是一个帮助器类,它惰性地为每个键创建钩子。构造函数接受一个工厂函数;第一次通过请求密钥 for(key),则调用工厂,并缓存生成的钩子。
const keyedHook = new HookMap((key) => new SyncHook(["arg"]));
插件使用 for(key) 获取特定键的钩子(按需创建),然后 tap 像往常一样:
keyedHook.for("some-key").tap("MyPlugin", (arg) => {
/* ... */
});
keyedHook.for("some-key").tapAsync("MyPlugin", (arg, callback) => {
/* ... */
});
keyedHook.for("some-key").tapPromise("MyPlugin", (arg) => {
/* ... */
});
的所有者 HookMap 使用 get(key) 查找现有的钩子而不创建钩子。这通常是在调用端首选的,这样没有插件关心的键永远不会被实现:
const hook = keyedHook.get("some-key");
if (hook !== undefined) {
hook.callAsync("arg", (err) => {
/* ... */
});
}
一个 HookMap 也可以被拦截。 intercept({ factory }) 包装工厂,以便您可以自定义或替换为每个新键返回的钩子。
代码生成
Tapable不会在调用时迭代轻击。相反,是第一次 call, callAsync 或 promise 在钩子被修改后调用,钩子会使用 new Function(...) 并在实例上缓存它。这就是README中“代码中的eval”的含义:钩子的调度逻辑作为字符串生成,并转化为引擎可以内联和优化的真正的JavaScript函数。
生成的函数被定制为:
- 调用类型 -主人是否打来电话
call(同步)callAsync(回调),或promise. 每一种都会产生不同的骨架。promise()把尸体裹起来new Promise((_resolve, _reject) => { ... })。 - 利用类型 -对于每次点击,生成器都会发出正确的调用模式:直接调用
tap的节点式回调包装tapAsync,.then(...)链接为tapPromise。 - 钩类 - - - - - -
SyncHook发出直线序列的呼叫;SyncBailHook发出提前返回支票;SyncWaterfallHook通过调用线程化一个值;循环钩子将主体包裹在一个重新进入的循环中;AsyncParallel*扇出水龙头并计算完井次数;AsyncSeries*链。 - 拦截器 -如果附加了拦截器,调用它们的
call/tap/loop/error/result/done处理程序被拼接到生成的主体中;否则,它们不需要任何成本。 - 上下文 ——一个
_context对象只有在至少有一个tap或拦截器选择使用context: true。 - 参数数量 -生成的代码硬编码了当钩子被构造时声明的参数的数量,所以没有
arguments/rest处理发生在运行时。
每当钩子的形状发生变化时,编译的函数就失效(重置回一次性“重新编译然后调用”trampoline) tap* 或 intercept 呼叫。因此,稳态调用直接通过缓存的函数运行,没有每个tap分支。
为什么这很重要?
- 您只需为您使用的功能付费。一个没有拦截器、只同步的钩子编译成一个简短的直接函数调用序列。
- 调试钩子意味着读取生成的源代码。如果你想看的话,
Hook.prototype.compile返回new Function(...)结果-日志hook._createCall("sync").toString()(或"async"/"promise")检查尸体。 - 因为分派是由代码生成的,所以钩子的行为是在编译时完全确定的。在注册后改变点击选项(例如,更改
stage在现有的Tap对象)在引起重新编译之前不会对抽头重新排序。
钩/ HookMap接口
Public(任何持有钩子引用的人都可以调用,比如插件):
interface Hook {
tap: (name: string | Tap, fn: (context?, ...args) => Result) => void;
tapAsync: (
name: string | Tap,
fn: (
context?,
...args,
callback: (err: Error | null, result: Result) => void
) => void
) => void;
tapPromise: (
name: string | Tap,
fn: (context?, ...args) => Promise<Result>
) => void;
intercept: (interceptor: HookInterceptor) => void;
withOptions: (
options: TapOptions
) => Omit<Hook, "call" | "callAsync" | "promise">;
}
interface HookInterceptor {
name?: string;
call?: (context?, ...args) => void;
loop?: (context?, ...args) => void;
tap?: (context?, tap: Tap) => void;
error?: (err: Error) => void;
result?: (result: any) => void;
done?: () => void;
register?: (tap: Tap) => Tap | undefined;
context?: boolean;
}
interface HookMap {
for: (key: any) => Hook;
intercept: (interceptor: HookMapInterceptor) => void;
}
interface HookMapInterceptor {
factory: (key: any, hook: Hook) => Hook;
}
interface Tap {
name: string;
type: "sync" | "async" | "promise";
fn: Function;
stage: number;
context: boolean;
before?: string | Array<string>;
}
受保护(只针对包含钩子的类——它拥有触发钩子的权利):
interface Hook {
isUsed: () => boolean;
call: (...args) => Result;
promise: (...args) => Promise<Result>;
callAsync: (
...args,
callback: (err: Error | null, result: Result) => void
) => void;
}
interface HookMap {
get: (key: any) => Hook | undefined;
for: (key: any) => Hook;
}
isUsed() 返回 true 当钩子至少注册了一个tap或interceptor时。当没有插件在监听时,钩子所有者可以使用它来跳过昂贵的参数准备:
class Car {
// ...
setSpeed(newSpeed) {
if (this.hooks.accelerate.isUsed()) {
this.hooks.accelerate.call(newSpeed);
}
}
// ...
}
MultiHook
一个 MultiHook 是一个向前的钩子式立面 tap, tapAsync, tapPromise, intercept, withOptions 同时连接几个底层钩子。它不会暴露 call* 方法——只有包装钩子的所有者决定它们何时运行。这是类公开“发生在这些事件中的任何一个”监听表面的典型方式,而无需插件将自己单独连接到每个钩子。
把一个龙头向几个钩子扇形展开
const { MultiHook, SyncHook } = require("tapable");
class Car {
constructor() {
const accelerate = new SyncHook(["newSpeed"]);
const brake = new SyncHook();
this.hooks = {
accelerate,
brake,
// `anyMovement` is not a real hook — it simply re-registers taps
// on both `accelerate` and `brake`.
anyMovement: new MultiHook([accelerate, brake])
};
}
}
const car = new Car();
car.hooks.anyMovement.tap("Telemetry", () => console.log("car moved"));
car.hooks.accelerate.call(42); // "car moved"
car.hooks.brake.call(); // "car moved"
这个 MultiHook 没有自己的状态:上面的水龙头在里面结束 accelerate.taps 和 brake.taps。
转发异步轻接
tapAsync / tapPromise 转发到每个包装的钩子——确保它们都是兼容的是插件的工作。注册一个 tapPromise 在一个 MultiHook 这就结束了。 SyncHook 将在注册时间抛出钩子。
const build = new AsyncSeriesHook(["stats"]);
const rebuild = new AsyncSeriesHook(["stats"]);
const anyBuild = new MultiHook([build, rebuild]);
anyBuild.tapPromise("Report", async (stats) => report.send(stats));
共享拦截器和选项
intercept 和 withOptions 也都转发了,所以呢 MultiHook 可以用来将相同的拦截器或预配置的选项附加到一组钩子上:
const anyBuild = new MultiHook([build, rebuild]);
anyBuild.intercept({
call: () => console.log("build started"),
done: () => console.log("build done")
});
// Every tap added through `late` is staged late on both underlying hooks.
const late = anyBuild.withOptions({ stage: 10 });
late.tap("RunLast", () => {
/* ... */
});
isUsed
isUsed() 返回 true 如果任何一个包裹的钩子至少有一个tap或interceptor,当没有人在监听它们时,所有者可以便宜地跳过工作:
if (this.hooks.anyMovement.isUsed()) {
// expensive telemetry payload is only built when a plugin actually cares
this.hooks.accelerate.call(computeSpeed());
}