概念
官方链接
tapable 类似于一个发布订阅中心,基本流程就是
- 注册自己需要的一个或多个类型的一个或多个 hook 的实例
- 为该实例注册消费者
- 在某个时机触发该 hook 实例,执行消费者
最终是让我们使用各种各样的 hook,tapable 是这个库的名字,我们实际使用的是这些 hook
tapable 库输出的有 9 种类型的 hook 类,如下index.js:
"use strict";
exports.__esModule = true;
exports.Tapable = require("./Tapable");
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");
HOOK 简介
执行效率
The Hook will compile a method with the most efficient way of running your plugins. hook 实例会自行判断执行注册的 plugin 消费者的最有效率的方法。 它依赖如下因素产生最终执行的逻辑:
- 注册的 plugin 的数目
- 注册的 plugin 的类型
- 执行的方法 sync async promise
- 参数的数目
- 有无使用注入【intercept api】
Hook 类型
执行顺序
每一个 hook 可以注册(被 tap tapAsync tapPromise
)多个函数【消费者】。如何执行依赖于 hook 本身的类型:
- Basic hook 只是简单执行所有注册的函数
- Waterfall 把上一个函数的返回值传入下一个函数
- Bail【迅速离开】允许早结束, 只要有一个函数有返回值, 后续的函数都不再执行
- Loop 只要有一个 plugin 有返回值,就从第一个 plugin 再开始执行,直到所有 plugin 都返回 undefined
同步异步
另外 hook 可以是同步或异步的,有如下 3 种:
- Sync 只能用 tap 方法注册同步的函数
- AsyncSeries 可以用 tap tapAsync tapPromise 方法对应注册 同步的、回调的、promise 的函数。注册的函数逐行调用【类似async await】
- AsyncParallel 可以用 tap tapAsync tapPromise 方法对应注册 同步的、回调的、promise 的函数。注册的函数并发调用。
【注:只不过,用 tapPromise 方法定义的消费者必须返回一个 promise】
组合结果及举例
最终 hook 的类型有上面2个方面组合而成, 共 9 种【可在 tapable 库的 index.js 看到罗列】,不到 12 种 , 比如没有 AsyncParallelWaterfallHook 类型,有 AsyncSeriesWaterfallHook。举2个例子如下:
- AsyncSeriesWaterfallHook 允许异步函数,并且按照顺序执行,并且把上一个函数的返回值传入下一个函数。
- AsyncSeriesHook 执行 call 的时候如果某一步不是 通过 tapPromise 注册的消费者,就不再往后执行了,因为它应该用的是 类似 aa,会一直在等待中。
intercept api 注入
所有的hook 都提供了 intercept api 该 api 传入一个对象,包含如下函数字段: context【boolean】 true 时可以用来传递任意值,暂时还不清楚具体用途。 call hook 触发的时候触发,代入 hook 触发时的参数 tap 注册函数的时候触发。传入的 tapobject 不可修改 loop loop hook 进入 loop 的时候触发 register 注册函数的时候触发。传入的 tapobject 可修改
注册消费者
共 3 种方式
共 tap tapPromise tapAsync
3 种方式。
- 同步的 hook 只能用 tap 方法添加 plugin
- 异步的 hook 同时支持异步的 plugin,即支持用 tap tapPromise tapAsync 3 种方法添加 plugin
关于tapPromise tapAsync
tapPromise 方法调用返回 promise。 tapAsync 方法可以在内部执行异步函数,不过需要传入callback去执行回调
触发消费
触发这些在 tap 等函数中定义的 plugin 需要用 call 等一系列方法
- tap 对应 call 方法
- tapPromise 对应 promise 方法
- tapAsync 对应 callAsync 方法
不过,AsyncParallelHook AsyncSeriesHook 等类型的 hook 没有 call 方法,只有 promise callAsync 方法,虽然可以用 tap 方法定义消费者。即:异步类型的 hook 可以用同步的 tap 方式添加消费者,不过该 hook 在被触发的时候不能用同步的 call 方法。【下方源码简介部分有解释】
源码简介
以 SyncHook 为例
注册消费者简析
tap tapPromise tapAsync
这几个如何注册消费者的函数是如何定义的?
- 通过
options = this._runRegisterInterceptors(options);
改造了下options
参数。不过如果没有给 hook 实例配置intercept api
的interceptor.register
的话,什么也不做。 - 通过
this._insert(options);
把新的消费者加到该 hook 实
例上。判断有没有设置 before stage
等参数,有的话重新排序,没有的话把新的消费者直接加到后面。
触发消费简析
在 /node_modules/_tapable@1.1.3@tapable/lib/Hook.js
中 call promise callAsync
这几个触发 hook 执行的函数如何定义的?
- 在上面 的
constructor
写的this.call=this._call
this.promise = this._promise;
this.callAsync = this._callAsync;
并不真的 执行,只是在实例化时执行。 - 而在最下面
Object.defineProperties
定了_call
_promise
_callAsync
方法,它们的值是函数。【不过为什么不直接放 class 里面呢?】 _call
是调用this._createCall
生成的函数,而this._createCall
中调用的是compile
方法,而syncHook
类覆盖了Hook
基类的compile
方法【该基类方法本身也要求被改写】SyncHook
类的compile
方法执行了factory.setup
和factory.create
。- 而
factory
是实例化的SyncHookCodeFactory
【继承自HookCodeFactory
】 得到的一个实例, factory.setup
执行的效果是 把taps
数组中的对象上的 fn 返回成一个数组赋值给SyncHook
实例的_x
属性【在getTapFn
函数中被使用,用于拼成生成的执行函数的字符串】,factory.create
使用的是new Function
【里面拼字符串作为函数功能】 的方式定义的函数,根据 type 为sync
async
或promise
来决定使用不同的字符串,里面约定了这几种不同的触发 hook 的方式如何调用注册的消费者。
【factory.create
生成的这几个被字符串拼接的函数是比较重要的,是在他们这决定如何执行被注册的消费者的。这里消费在上面 注册消费者简析 中介绍的排好序的被注册的消费者】
以 AsyncParallelHook 为例
为什么他触发执行消费者的方法中没有 call 方法呢?
他是在 /node_modules/_tapable@1.1.3@tapable/lib/AsyncParallelHook.js
中在最下面通过 Object.defineProperties(AsyncParallelHook.prototype
给
给 _call
设置的 value
为 undefined
,从而覆盖了 /node_modules/_tapable@1.1.3@tapable/lib/Hook.js
基类中定义的 _call
方法,那 undefined
肯定不能被执行啊。
结合官方 demo 写一个小 demo 用于跑断点测试
略长,有耐心可以细看
代码如下:
// https://github.com/webpack/tapable
var {SyncHook, AsyncParallelHook, AsyncSeriesHook} = require('tapable')
/**
* 这里传入的数组,就是个占位符,或者语义化的值,
* 表示这个实例化的 hook 在 tap时候的回调函数写几个参数,
* 也即在被注册消费者、以及被调用的时候传入多少个参数。
*/
// const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// console.log('hook', hook)
class Car {
constructor() {
/**
* You won't get returned value from SyncHook or AsyncParallelHook,
* to do that, use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively
*
* SyncHook AsyncParallelHook 没有返回值,SyncWaterfallHook 和 AsyncSeriesWaterfallHook 有
**/
// 把实例化的 hook 放一块
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
brake: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "callback"])
};
}
/* 下面的方法都是这么触发 hook,从而让其上的消费者执行 */
brake(newSpeed) {
// following call returns undefined even when you returned values
// brake 是个 SyncHook,下面这个 call 返回 undefined
this.hooks.brake.call();
}
setSpeed(newSpeed) {
// following call returns undefined even when you returned values
// accelerate 是个 SyncHook,下面这个 call 返回 undefined
this.hooks.accelerate.call(newSpeed);
}
useNavigationSystemPromise(source, target) {
// 用 promise 方法可以触发所有用 tapPromise tapAsync tap 注册的消费者
return this.hooks.calculateRoutes.promise(source, target, ()=>{
console.log('useNavigationSystemPromise 执行1')
}).then((res) => {
// 虽然所有方法都能触发,但是只有所有消费者都是用 tapPromise 注册的话,才会执行到这里
// res is undefined for AsyncParallelHook
console.log('useNavigationSystemPromise 执行2 res', res)
});
}
// 用 callAsync 方法可以触发所有用 tapPromise tapAsync tap 注册的消费者
useNavigationSystemAsync(source, target) {
this.hooks.calculateRoutes.callAsync(source, target, err => {
console.log('useNavigationSystemAsync 执行')
});
}
}
const myCar = new Car();
/**
* 下面是给 hook 们注册消费者
* 用 tap 方法给 hook 添加一个消费者
* tap 要求必须要传入一个 name 来标记是什么 plugin 或触发原因
*
*/
myCar.hooks.brake.tap("WarningLampPlugin", () => {
console.log('刹车灯 on')
});
myCar.hooks.brake.tap("speeddown", () => {
console.log('speeddown on')
});
// 回调函数可以接受参数【可不止一个】
myCar.hooks.accelerate.tap('loggerPlugin', newSpeed => {
console.log(`正则加速到 ${newSpeed}`)
})
/**
* calculateRoutes 的消费者1
*
* calculateRoutes 是个 AsyncParallelHook
* 可以用 tap tapAsync tapPromise 方法
* 这里用的 tapPromise 方法注册,后面的函数返回值应该是 promise
* 如果不返回一个 promise 的话,会报 Error: Tap function (tapPromise) did not return promise (returned undefined)
*
*/
myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, callback) => {
// return a promise
console.log('in GoogleMapsPlugin')
callback()
return Promise.resolve('google')
});
/**
* calculateRoutes 的消费者2
*
* calculateRoutes 是个 AsyncParallelHook
* 可以用 tap tapAsync tapPromise 方法
* 这里用的 tapAsync 方法注册,后面的函数中用的是回调函数的方式
*
*/
myCar.hooks.calculateRoutes.tapAsync({
name: 'BingMapsPlugin',
before: 'GoogleMapsPlugin', // 消费者被调用的顺序。before 可以是字符串或数组, 是在 node_modules/_tapable@1.1.3@tapable/lib/Hook.js:117 的 _insert 函数中处理的顺序关系
}, (source, target, callback) => {
console.log('in BingMapsPlugin')
callback()
});
/**
* calculateRoutes 的消费者3
*
* calculateRoutes 是个 AsyncParallelHook
* 可以用 tap tapAsync tapPromise 方法
* 这里用的 tap 方法注册,后面的函数中用的是回调函数的方式
*
* You can still use sync plugins
* AsyncParallelHook 依然可以用同步方法 tap 来添加 plugin
*
* 最终 calculateRoutes 在被触发的时候会并发执行注册的这 3 个方法
*
*/
myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, callback) => {
console.log('in CachedRoutesPlugin')
callback()
})
// 这里是实际调用 car 的方法,brake 方法中执行了 hook 的 call,去触发该 hook
myCar.brake()
// myCar.setSpeed(100)
// myCar.useNavigationSystemPromise('source', 'target')
myCar.useNavigationSystemAsync('source', 'target')
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!