# tapable 2.0.0-beta.8
在tapable0.x中, 需要继承tapable才能使用它的钩子. tapable2.x更加专注于钩子本身, 使用它更加直接.
const { SyncHook } = require('tapable')
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(['newSpeed'])
}
}
setSpeed(newSpeed) {
this.hooks.accelerate.call(newSpeed)
}
}
const myCar = new Car()
myCar.hooks.accelerate.tap('LoggerPlugin', newSpeed =>
console.log(`Accelerating to ${newSpeed}`)
)
myCar.setSpeed(666) // Accelerating to 666
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# SyncHook UML
const factory = new SyncHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
以SyncHook为例, Hook相当于一个抽象类, SyncHook在其基础上, 提供自己的compile, tapAsync, tapPromise.
在SyncHook实例上注册的回调函数是同步调用的, 通过tap来注册, 由其基类Hook来提供方法, 调用tapAsync, tapPromise则抛出错误.
# SyncHook
以SyncHook为例
const SyncHook = require("./lib/SyncHook.js");
const sh = new SyncHook(["p1", "p2"]);
sh.tap(
{
name: "mySyncHookPlugin1",
stage: 2
},
(p1, p2) => {
console.log("plugin1", "p1", p1, "p2", p2);
}
);
sh.tap(
{
name: "mySyncHookPlugin2",
stage: 0
},
(p1, p2) => {
console.log("plugin2", "p1", p1, "p2", p2);
}
);
sh.call(1, 2);
// plugin2 p1 1 p2 2
// plugin1 p1 1 p2 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
tap
方法将会收集注册的回调函数(同步), tap继承自Hook
call
方法执行所收集的回调函数, call继承自Hook
如果是tapable0.x, 这些回调函数会被收集起来, 并遍历进行调用.
而tapable2.x则利用new Function
来执行生成的代码
上面的例子, 生成的代码如下
function anonymous(p1, p2
) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(p1, p2);
var _fn1 = _x[1];
_fn1(p1, p2);
}
2
3
4
5
6
7
8
9
10
# tap
Hook
tap(options, fn) {
this._tap("sync", options, fn);
}
_tap(type, options, fn) {
//...
options = Object.assign({ type, fn }, options);
// 运行拦截器, 类似axios里的拦截器
options = this._runRegisterInterceptors(options);
this._insert(options);
}
_insert(item) {
//...
this.taps[i] = item;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
tap
内部调用_insert
来将回调函数收集到taps
中, 首先看下_insert
的主要逻辑
_insert(item) {
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
默认插件的stage
为0 , 根据stage
从小到大, 使用插入排序
而完整的_insert
还由一个before
变量来控制排序, before
优先级比stage
高.
_insert(item) {
this._resetCompilation();
let before;
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# call
在使用tap
收集完回调后, 使用call
则可以执行这些回调函数, 但是并不是通过遍历taps
数组执行, 而是使用call
去生成一个函数, 并执行
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
// 执行生成的函数
return this.call(...args);
};
class Hook {
constructor(args = [], name = undefined) {
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
}
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
_insert(item) {
this._resetCompilation();
// ..
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
首次调用call
则会调用CALL_DELEGATE
, 会将this.call
使用this._createCall("sync")
覆盖, 避免重复调用CALL_DELEGATE
去生成函数. 但是如果在调用call
之后, 又通过tap
去添加回调函数, 则会进行reset, 重新执行CALL_DELEGATE
# _createCall
Hook
class Hook {
compile(options) {
throw new Error("Abstract: should be overridden");
}
_createCall(type) {
return this.compile({
taps: this.taps, // [{ type, fn, name }]
interceptors: this.interceptors, // []
args: this._args, // like ['p1', 'p2']
type: type // 'sync' | 'async' | 'promise'
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
之前又提到Hook
是一个抽象类, 它的一些方法需要子类去实现, SyncHook
实现了compile
SyncHook
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SyncHookCodeFactory
继承自HookCodeFactory
, 并提供content
方法给父类方法使用.
# factory.setup
class HookCodeFactory {
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
}
2
3
4
5
setup
方法, 将taps
中的回调函数保存在SyncHook
实例的_x
# factory.create
整体结构如下, 分别对tap
, async
, promise
执行不同的逻辑来生成执行函数.
class HookCodeFactory {
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
case "promise":
}
this.deinit();
return fn;
}
init(options) {
this.options = options;
this._args = options.args.slice();
}
deinit() {
this.options = undefined;
this._args = undefined;
}
contentWithInterceptors(options) {
if (this.options.interceptors.length > 0) {
//...
} else {
return this.content(options);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
对于call
, 执行的逻辑如下,
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
2
3
4
5
6
7
8
9
10
11
12
new Function
函数的形参由this.args()
得到
比如我们的例子里, ['p1', 'p2']
需要转化成 'p1,p2'
, 对于callAsync
我们还需多注入一个callback的参数, 如果考虑context
, 这里也需要做处理.
class HookCodeFactory {
args({ before, after } = {}) {
let allArgs = this._args;
if (before) allArgs = [before].concat(allArgs);
if (after) allArgs = allArgs.concat(after);
if (allArgs.length === 0) {
return "";
} else {
return allArgs.join(", ");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function
函数体由this.header() + this.content()
得到
class HookCodeFactory {
header() {
let code = "";
if (this.needContext()) {
code += "var _context = {};\n";
} else {
code += "var _context;\n";
}
code += "var _x = this._x;\n";
if (this.options.interceptors.length > 0) {
//...
}
for (let i = 0; i < this.options.interceptors.length; i++) {
//...
}
return code;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
header
里生成的代码主要是拿到this._x
, 其包含里待执行的回调函数, 因为最终生成的函数是由SyncHook
实例调用call
方法执行的, 所以this
指向SyncHook
实例.
SyncHookCodeFactory
提供了content
方法的实现
class SyncHookCodeFactory extends HookCodeFactory {
// {
// onError: err => `throw ${err};\n`,
// onResult: result => `return ${result};\n`,
// resultReturns: true,
// onDone: () => "",
// rethrowIfPossible: true
// }
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
各个子类Hook其args
, header
的代码是通用的, 而content
则由各个子类自己实现.
← tapable 0.2.8 devtool →