Tapable是一个为插件创造钩子的库,他也是webpack的核心库。Tapable v1以后的版本跟以前的用法差异很是大,主要区别是之前用继承class A extends tapable, 如今直接在类里面定义私有成员this.hooks. 貌似网上不少都是老版本的用法,鉴于立刻就要v2了,翻译走一波,顺便点一下目前1.1版本的坑
原文github.com/webpack/tap…
tapable提供不少钩子类(Hook classes),他们能够被用来为插件创造钩子。javascript
const { SyncHook, // 同步钩子 SyncBailHook, // 同步早退钩子 SyncWaterfallHook, // 同步瀑布钩子 SyncLoopHook, // 同步循环钩子 AsyncParallelHook, // 异步并发钩子 AsyncParallelBailHook, // 异步并发可早退钩子 AsyncSeriesHook, // 异步顺序钩子 AsyncSeriesBailHook, // 异步顺序可早退钩子 AsyncSeriesWaterfallHook // 异步顺序瀑布钩子 } = require("tapable");复制代码
npm install --save tapable复制代码
全部的钩子类的构造器都接受一个可选参数,它是一个 这个钩子所接受参数的参数名数组。java
const hook = new SyncHook(["arg1", "arg2", "arg3"]);复制代码
最佳作法是一次性在hooks属性里面定义好所用的钩子:webpack
class Car { constructor() { this.hooks = { // 如下分别是油门,刹车,计算路线钩子 accelerate: new SyncHook(["newSpeed"]), brake: new SyncHook(), calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"]) }; } /* ... */ }复制代码
其余人如今就能使用以上的钩子了:git
const myCar = new Car(); // 使用tap方法添加具体的执行逻辑 myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on()); // 亮灯插件,逻辑为刹车时亮灯复制代码
为了定位你的插件,一个合适的名字(上面WarningLampPlugin)是必须的。github
你定义的函数能够接收参数web
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));复制代码
对于同步的钩子,tap是仅有的添加插件的有效方法。异步钩子还支持异步插件,除了tap外,还有tapPromise,tapAsync等方法。typescript
myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => { // 谷歌的找路线的异步方法返回promise return google.maps.findRoute(source, target).then(route => { routesList.add(route); }); }); myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => { // bing的找路线异步方法用的callback方式 bing.findRoute(source, target, (err, route) => { if(err) return callback(err); routesList.add(route); // call the callback callback(); }); }); // 异步钩子也可使用同步方法,好比下例取出缓存的版本 myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => { const cachedRoute = cache.get(source, target); if(cachedRoute) routesList.add(cachedRoute); })复制代码
而后声明了这些钩子的类须要用他们时:npm
class Car { /* 我做为一辆车,我只在意我有如下功能,但这些功能的具体实现交给了第三方, * 我给这些第三方提供能修改逻辑的权限就行了 */ setSpeed(newSpeed) { // 下面的call没有返回值 this.hooks.accelerate.call(newSpeed); } useNavigationSystemPromise(source, target) { const routesList = new List(); return this.hooks.calculateRoutes.promise(source, target, routesList).then((res) => { // res是undefined return 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()); }); } }复制代码
(注:此处例子用的是SyncHook和AsyncParallelHook, 因此他们是没有返回值的,即便你返回了也只能获得undefined。要想获得返回值请用SyncWaterfallHook和AsyncSeriesWaterfallHook!并且注意waterfall钩子总会返回值(即便你不return))数组
咱们会用最高效的方式编译一个运行你提供的插件的方法,生成的代码取决于:promise
myCar.hooks.calculateRoutes.intercept({ call: (source, target, routesList) => { console.log("Starting to calculate routes"); }, register: (tapInfo) => { // tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... } console.log(`${tapInfo.name} is doing its job`); return tapInfo; // may return a new tapInfo object } })复制代码
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` 从一个空对象开始若是至少有一个插件里写了 `context: true`. // 若是没有插件定义 `context: true`, 那么 `context` 是 undefined. if (context) { // 你能够添加任意值,以后的插件都能访问到. context.hasMuffler = true; } } }); myCar.hooks.accelerate.tap({ name: "NoisePlugin", context: true }, (context, newSpeed) => { if (context && context.hasMuffler) { console.log("Silence..."); } else { console.log("Vroom!"); } });复制代码
const keyedHook = new HookMap(key => new SyncHook(["arg"]))复制代码
keyedHook.tap("some-key", "MyPlugin", (arg) => { /* ... */ }); keyedHook.tapAsync("some-key", "MyPlugin", (arg, callback) => { /* ... */ }); keyedHook.tapPromise("some-key", "MyPlugin", (arg) => { /* ... */ });复制代码
const hook = keyedHook.get("some-key"); if(hook !== undefined) { hook.callAsync("arg", err => { /* ... */ }); }复制代码
公有的
interface Hook { tap: (name: string | Tap, fn: (context?, ...args) => Result) => void, tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void, tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void, intercept: (interceptor: HookInterceptor) => void } interface HookInterceptor { call: (context?, ...args) => void, loop: (context?, ...args) => void, tap: (context?, tap: Tap) => void, register: (tap: Tap) => Tap, context: boolean } interface HookMap { for: (key: any) => Hook, tap: (key: any, name: string | Tap, fn: (context?, ...args) => Result) => void, tapAsync: (key: any, name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void, tapPromise: (key: any, name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void, intercept: (interceptor: HookMapInterceptor) => void } interface HookMapInterceptor { factory: (key: any, hook: Hook) => Hook } interface Tap { name: string, type: string fn: Function, stage: number, context: boolean }复制代码
protected(定义钩子的类才能用的)
interface Hook { isUsed: () => boolean, call: (...args) => Result, promise: (...args) => Promise<Result>, callAsync: (...args, callback: (err, result: Result) => void) => void, } interface HookMap { get: (key: any) => Hook | undefined, for: (key: any) => Hook }复制代码
这是一个像钩子的类,用来重定向钩子的插件到其余钩子:
const { MultiHook } = require("tapable"); this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);复制代码