搜索引擎搜索tapable中文文档,你会看见各类翻译,点进去一看,确实是官方的文档翻译过来的,可是webpack的文档确实还有不少须要改进的地方,既然是开源的为何不去github上的tapable库看呢,一看,确实,比webpack文档上的描述得清楚得多.node
tapable 是一个相似于nodejs 的EventEmitter 的库, 主要是控制钩子函数的发布与订阅,控制着webpack的插件系.webpack的本质就是一系列的插件运行.webpack
Tapable库 提供了不少的钩子类, 这些类能够为插件建立钩子git
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable");
npm install --save tapable
全部的钩子构造函数,都接受一个可选的参数,(这个参数最好是数组,不是tapable内部也把他变成数组),这是一个参数的字符串名字列表github
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
最好的实践就是把全部的钩子暴露在一个类的hooks属性里面:web
class Car { constructor() { this.hooks = { accelerate: new SyncHook(["newSpeed"]), brake: new SyncHook(), calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"]) }; } /* ... */ }
其余开发者如今能够这样用这些钩子npm
const myCar = new Car(); // Use the tap method to add a consument // 使用tap 方法添加一个消费者,(生产者消费者模式) myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
这须要你传一个名字去标记这个插件:数组
你能够接收参数promise
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
在同步钩子中, tap 是惟一的绑定方法,异步钩子一般支持异步插件异步
// promise: 绑定promise钩子的API myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => { // return a promise return google.maps.findRoute(source, target).then(route => { routesList.add(route); }); }); // tapAsync:绑定异步钩子的API 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 // tap: 绑定同步钩子的API myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => { const cachedRoute = cache.get(source, target); if(cachedRoute) routesList.add(cachedRoute); })
类须要调用被声明的那些钩子async
class Car { /* ... */ setSpeed(newSpeed) { // call(xx) 传参调用同步钩子的API this.hooks.accelerate.call(newSpeed); } useNavigationSystemPromise(source, target) { const routesList = new List(); // 调用promise钩子(钩子返回一个promise)的API return this.hooks.calculateRoutes.promise(source, target, routesList).then(() => { return routesList.getRoutes(); }); } useNavigationSystemAsync(source, target, callback) { const routesList = new List(); // 调用异步钩子API this.hooks.calculateRoutes.callAsync(source, target, routesList, err => { if(err) return callback(err); callback(null, routesList.getRoutes()); }); } }
tapable会用最有效率的方式去编译(构建)一个运行你的插件的方法,他生成的代码依赖于一下几点:
这些肯定了尽量快的执行.
每个钩子均可以tap 一个或者多个函数, 他们如何运行,取决于他们的钩子类型
此外,钩子能够是同步的,也能够是异步的,Sync, AsyncSeries 和 AsyncParallel ,从名字就能够看出,哪些是能够绑定异步函数的
myHook.tap()
, myHook.tapAsync()
和 myHook.tapPromise()
.).他会按顺序的调用每一个方法.全部钩子都提供额外的拦截器API
// 注册一个拦截器 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 } })
call:(...args) => void
当你的钩子触发以前,(就是call()以前),就会触发这个函数,你能够访问钩子的参数.多个钩子执行一次
tap: (tap: Tap) => void
每一个钩子执行以前(多个钩子执行多个),就会触发这个函数
loop:(...args) => void
这个会为你的每个循环钩子(LoopHook, 就是类型到Loop的)触发,具体何时没说
register:(tap: Tap) => Tap | undefined
每添加一个Tap
都会触发 你interceptor上的register,你下一个拦截器的register 函数获得的参数 取决于你上一个register返回的值,因此你最好返回一个 tap 钩子.
插件和拦截器均可以选择加入一个可选的 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`. // 若是最少有一个插件使用 `context` 那么context 一开始是一个空的对象 // If no plugins use `context: true`, then `context` is undefined // 如过tap进去的插件没有使用`context` 的 那么内部的`context` 一开始就是undefined if (context) { // Arbitrary properties can be added to `context`, which plugins can then access. // 任意属性均可以添加到`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!"); } });
一个 HookMap是一个Hooks映射的帮助类
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 => { /* ... */ }); }
Public(权限公开的):
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 }
把其余的Hook 重定向(转化)成为一个 MultiHook
const { MultiHook } = require("tapable"); this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);
OK 全部的内容我都已翻译完成.
其中有不少不是直译,这样写下来感受就是按照原文的脉络从新写了一遍....,应该能更清楚明白,要不是怕丢脸我就给个原创了,哈哈.
以后, 我还会写一篇完整的原创解析,直击源码,搞定tapable, 彻底了解webpack插件系统(webpack原本就是一个插件的事件流), 很久没写原创了. 我本身也很期待.