前言
本篇正式进入到源码解读。
整体看,Tapable 的源码并不复杂,核心就两个类 Hook 和 HookCodeFactory。
先以 SyncHook 为例,看一下它的内部实现,逐渐引入到 Hook 和 HookCodeFactory 的内部实现逻辑上。
最后说一下两个帮助类 HookMap 和 MultiHook 。
源码
以 SyncHook 为例,看一下它的实现。
SyncHook
此类的实现在文件 tapable/lib/SyncHook.js 中。
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
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;
}
SyncHook.prototype = null;
module.exports = SyncHook;
复制代码
整体逻辑
- 先定义类 SyncHookCodeFactory 并继承 HookCodeFactory,HookCodeFactory 是生成可执行代码的类
- 接着实例化 SyncHookCodeFactory
- 然后定义 TAP_ASYNC 、TAP_PROMISE 和 COMPILE 方法。因为 SyncHook 是同步的,没有异步订阅的功能,所以使用 tapAsync 和 tapPromise 订阅时,抛出错误
- 定义同步 SyncHook 函数,并导出
SyncHook 方法内部逻辑
- 通过 new Hook 实例化 Hook
- 然后修改实例化对象的构造函数对象
- 重写实例化对象的 tapAsync 和 tapPromise 方法
- 重写实例化对象的 compile 方法
- 返回实例化对象
我们先忽略 Hook 和 HookCodeFactory 类的具体实现,整个文件的逻辑就是一个完整的套路。
其他的 Hook 也是按照找个套路实现,差别只在继承 HookCodeFactory 的类上以及它们各自的 content 方法的实现。
比如 AsyncSeriesLoopHook 的实现如下:
代码在文件 tapable/lib/AsyncSeriesLoopHook.js
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class AsyncSeriesLoopHookCodeFactory extends HookCodeFactory {
content({ onError, onDone }) {
return this.callTapsLooping({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
}
const factory = new AsyncSeriesLoopHookCodeFactory();
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
function AsyncSeriesLoopHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = AsyncSeriesLoopHook;
hook.compile = COMPILE;
hook._call = undefined;
hook.call = undefined;
return hook;
}
AsyncSeriesLoopHook.prototype = null;
module.exports = AsyncSeriesLoopHook;
复制代码
AsyncSeriesLoopHook 是异步方法,所以重置了它实例化对象上的 call 和 _call 方法即不能使用 call 方法进行调用。
从源码确定两点
查看源码可以确定两点:
- 同步 Hook 是不能通过 tapAsync 和 tapPromise 进行订阅的,因为这两个方法被重置为:
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");
};
复制代码
- 异步 Hook 是不能通过 call 方法进行调用的,因为它被重置为:
hook._call = undefined;
hook.call = undefined;
复制代码
Hook 类
此类的定义在文件 tapable/lib/Hook.js 中。
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
const CALL_ASYNC_DELEGATE = function(...args) {
this.callAsync = this._createCall("async");
return this.callAsync(...args);
};
const PROMISE_DELEGATE = function(...args) {
this.promise = this._createCall("promise");
return this.promise(...args);
};
class Hook {
constructor(args = [], name = undefined) {
this._args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this._promise = PROMISE_DELEGATE;
this.promise = PROMISE_DELEGATE;
this._x = undefined;
this.compile = this.compile;
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
}
compile(options) {
throw new Error("Abstract: should be overridden");
}
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
_tap(type, options, fn) {
if (typeof options === "string") {
options = {
name: options.trim()
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
options = Object.assign({ type, fn }, options);
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tap(options, fn) {
this._tap("sync", options, fn);
}
tapAsync(options, fn) {
this._tap("async", options, fn);
}
tapPromise(options, fn) {
this._tap("promise", options, fn);
}
_runRegisterInterceptors(options) {
for (const interceptor of this.interceptors) {
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) {
options = newOptions;
}
}
}
return options;
}
withOptions(options) {
const mergeOptions = opt =>
Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
return {
name: this.name,
tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
intercept: interceptor => this.intercept(interceptor),
isUsed: () => this.isUsed(),
withOptions: opt => this.withOptions(mergeOptions(opt))
};
}
isUsed() {
return this.taps.length > 0 || this.interceptors.length > 0;
}
intercept(interceptor) {
this._resetCompilation();
this.interceptors.push(Object.assign({}, interceptor));
if (interceptor.register) {
for (let i = 0; i < this.taps.length; i++) {
this.taps[i] = interceptor.register(this.taps[i]);
}
}
}
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
_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;
}
}
复制代码
在构造函数中,定义了 tap 、 tapAsync 和 tapPromise 方法,这是那个方法是我们订阅函数时所使用的方法。
这三个方法在原型链上都调用了 this._tap 的私有方法,唯一不同的是第一个参数 sync 、async 和 promise 代表了不同的订阅函数类型。
_tap 方法
_tap(type, options, fn) {
if (typeof options === "string") {
options = {
name: options.trim()
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
options = Object.assign({ type, fn }, options);
options = this._runRegisterInterceptors(options);
this._insert(options);
}
复制代码
私有方法 _tap 主要功能是序列化 options 并添加到 taps 数组中。
实现逻辑:
- 检查参数 options 的类型,options 最终还是一个对象,而且必须要有 name 属性
- 如果 options 有 context 属性,会调用 deprecateContext 方法,提示即将废弃 context 属性
- 调用 assign 方法合并 options,这样它就有了 name 、type 和 fn 属性
- 调用私有方法 _runRegisterInterceptors 修改 options
- 调用私有方法 _insert 调整订阅函数的顺序并存放在 taps 数组中
_runRegisterInterceptors 方法
_runRegisterInterceptors(options) {
for (const interceptor of this.interceptors) {
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) {
options = newOptions;
}
}
}
return options;
}
复制代码
私有方法 _runRegisterInterceptors 主要功能是修改 options 对象。
实现逻辑
- 遍历所有拦截器,如果有 register 属性方法,就调用它并传递原始的 options 作为参数,如果返回值不是 undefined 就修改 options,最后一个拦截器的 register 修改后的 options 就是最后最终的 options
所有拦截器的 register 属性方法有修改订阅函数 Tap 对象的能力。
_insert 方法
_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;
}
复制代码
私有方法 _run 主要功能是根据 before 或 stage 的值调整订阅函数的顺序,最终存储在 taps 数组中。
实现逻辑
- 先把 before 转换为 Set
- 检查 stage 如果是数字类型,就赋值给变量 stage
- 从 taps 末尾开始遍历
- 先让 i–
- 获取 i 位置的值 x
- 在把 x 赋值到 i + 1 的位置,这样就把 x 向后移动了一个位置
- 获取 x 的 stage
- 如果有 before
- 判断如果 x 的 name 在 before 中存在,也就是当前待插入的 item 要放在 x 之前,就 continue ,就再执行 while
- 如果没有 before,判断 x 的 stage 是否大于 item 的 stage,如果大于就 continue
- 让 i++ ,结束循环,这样就找到了应该插入的索引位置
- 把 item 存放在 i 的位置上
用图来表示一下上面的逻辑:
call、callAsync 和 promise 方法
这三个方法都是调用了 _createCall 私有方法,然后把返回值重新赋值给相应的方法。
比如 call 方法
this.call = CALL_DELEGATE;
复制代码
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
复制代码
_createCall 方法
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
复制代码
此方法调用了 compile 方法。
回到文章的开头,每个 Hook 类 在实例化后都会重写 compile 方法,也就是进入到了 HookCodeFactory 的逻辑。
HookCodeFactory 类
示例代码
我们以下面为示例,来看一下内部是怎么进入到 HookCodeFactory 逻辑的。
const {
SyncHook
} = require('tapable');
const hook = new SyncHook(['name', 'age']);
hook.tap({
name: 'js'
}, (name, age) => {
console.log('js', name, age);
});
hook.tap({
name: 'css'
}, (name, age) => {
console.log('css', name, age);
});
hook.tap({
name: 'node'
}, (name, age) => {
console.log('node', name, age);
});
hook.call('naonao', 2);
复制代码
在上篇文章中已经对 hook.tap 的实现,进行了源码解析。
我们看一下 hook.call 的执行。
在 class Hook 的构造函数中有:
this.call 定义
this.call = CALL_DELEGATE;
复制代码
CALL_DELEGATE 定义
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
复制代码
_createCall 定义:
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
复制代码
说明几点
这个地方说明几点:
- this.taps 存储的内容类似如下:
[
{type: 'sync', fn: f, name: 'js'},
{type: 'sync', fn: f, name: 'css'},
{type: 'sync', fn: f, name: 'node'}
]
复制代码
- this.interceptors 存储的内容是拦截器相关的对象,也是一个数组
- this._args 是类实例化时传入的 args,类似如下:
['name', 'age']
复制代码
- type 是 sync 、async 或 promise 中的一个
this.compile 定义
this.compile 方法是每个 Hook 在实例化后被重写的。
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
// 省略部分代码...
hook.compile = COMPILE;
return hook;
}
复制代码
COMPILE 内部逻辑
- 调用 setup 方法
- 调用 create 方法并返回
create 方法的返回结果最终会赋值给 CALL_DELEGATE 方法的 this.call,
再执行 this.call。
以上就把 hook.call 的执行逻辑梳理完毕了。
下面我们来看一下 COMPILE 方法内部执行的 setup 和 create 方法。
setup
setup 方法在 HookCodeFactory 中定义如下:
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
复制代码
它对 taps 进行 map 处理,把 fn 抽离出来,赋值给当前 Hook 实例的 _x 属性。
create
create 方法在 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":
fn = new Function(
this.args({
after: "_callback"
}),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `_callback(${err});\n`,
onResult: result => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
})
);
break;
case "promise":
let errorHelperUsed = false;
const content = this.contentWithInterceptors({
onError: err => {
errorHelperUsed = true;
return `_error(${err});\n`;
},
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
let code = "";
code += '"use strict";\n';
code += this.header();
code += "return new Promise((function(_resolve, _reject) {\n";
if (errorHelperUsed) {
code += "var _sync = true;\n";
code += "function _error(_err) {\n";
code += "if(_sync)\n";
code +=
"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
code += "else\n";
code += "_reject(_err);\n";
code += "};\n";
}
code += content;
if (errorHelperUsed) {
code += "_sync = false;\n";
}
code += "}));\n";
fn = new Function(this.args(), code);
break;
}
this.deinit();
return fn;
}
复制代码
它是根据 type 不同,拼接出不同的代码字符串并且通过 new Function 创建一个函数,赋值给 fn,最后返回 fn。
所以在之前,打印出 hook.call 方法是完全能看出订阅函数的执行顺序和流程的。
sync
先看 type 是 sync 的 case 分支:
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;
复制代码
主要逻辑如下:
- 调用 this.args 方法,获取函数参数
- 从 “use strict” 开始就是拼接函数主体的字符串
this.args 方法
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(", ");
}
}
复制代码
把 this._args 数组按照 **, ** 拼接为字符串
this.header 方法
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) {
code += "var _taps = this.taps;\n";
code += "var _interceptors = this.interceptors;\n";
}
return code;
}
复制代码
主要逻辑
- 调用 this.needContext 方法,判断是否需要 context 对象。如果需要就定义 context 是一个空对象。判断的根据就是 this.taps 中是否有某个成员配置了 context: true 也即类似 { type: ‘sync’, fn: f, name: ‘js’, context: true }。
- 定义缓存 _x 是 this._x,也就是订阅函数 fn 组成的数组
- 判断是否有拦截器,如果有定义缓存响应的 _taps 和 _interceptors。
- 返回拼接的字符串 code
this.contentWithInterceptors 方法
contentWithInterceptors(options) {
if (this.options.interceptors.length > 0) {
const onError = options.onError;
const onResult = options.onResult;
const onDone = options.onDone;
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
code += `${this.getInterceptor(i)}.call(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
code += this.content(
Object.assign(options, {
onError:
onError &&
(err => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.error) {
code += `${this.getInterceptor(i)}.error(${err});\n`;
}
}
code += onError(err);
return code;
}),
onResult:
onResult &&
(result => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.result) {
code += `${this.getInterceptor(i)}.result(${result});\n`;
}
}
code += onResult(result);
return code;
}),
onDone:
onDone &&
(() => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.done) {
code += `${this.getInterceptor(i)}.done();\n`;
}
}
code += onDone();
return code;
})
})
);
return code;
} else {
return this.content(options);
}
}
复制代码
- 先看一下没有没有拦截器的逻辑即 else 逻辑,直接返回了 this.content 的结果。
this.content 是定义在
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
复制代码
可以看出它是调用了 this.callTapsSeries 并返回了其结果。
- 再看一下有拦截器的情况:
- 先 for 循环所有的拦截器,如果配置了 call 属性方法,就拼接出每个拦截器执行 call 方法的字符串
- 还是执行 this.content 方法,只是参数是用原始的 options 和 拦截器上每一个 error 、result 和 done 可执行代码字符串拼接后的合并结果
callTapsSeries
callTapsSeries({
onError,
onResult,
resultReturns,
onDone,
doneReturns,
rethrowIfPossible
}) {
if (this.options.taps.length === 0) return onDone();
const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
const somethingReturns = resultReturns || doneReturns;
let code = "";
let current = onDone;
let unrollCounter = 0;
for (let j = this.options.taps.length - 1; j >= 0; j--) {
const i = j;
const unroll =
current !== onDone &&
(this.options.taps[i].type !== "sync" || unrollCounter++ > 20);
if (unroll) {
unrollCounter = 0;
code += `function _next${i}() {\n`;
code += current();
code += `}\n`;
current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
}
const done = current;
const doneBreak = skipDone => {
if (skipDone) return "";
return onDone();
};
const content = this.callTap(i, {
onError: error => onError(i, error, done, doneBreak),
onResult:
onResult &&
(result => {
return onResult(i, result, done, doneBreak);
}),
onDone: !onResult && done,
rethrowIfPossible:
rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
});
current = () => content;
}
code += current();
return code;
}
复制代码
主要功能是倒序遍历 taps,先忽略异步的情况。
重点看 this.callTap 部分,调用它把返回结果赋值给 content,然后重置 current 是 () => content; 这样利用闭包的特性,就缓存了上一次拼接出的字符串。
而每次循环时,又会把 current 赋值给 done ,在调用 this.callTap 内部会执行 done 方法,把它的结果拼接在相应的位置。
最后返回拼接的字符串 code 。
callTap
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
let hasTapCached = false;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.tap) {
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
hasTapCached = true;
}
code += `${this.getInterceptor(i)}.tap(${
interceptor.context ? "_context, " : ""
}_tap${tapIndex});\n`;
}
}
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`;
code += "try {\n";
}
if (onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
}
if (!rethrowIfPossible) {
code += "} catch(_err) {\n";
code += `_hasError${tapIndex} = true;\n`;
code += onError("_err");
code += "}\n";
code += `if(!_hasError${tapIndex}) {\n`;
}
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
if (!rethrowIfPossible) {
code += "}\n";
}
break;
case "async":
let cbCode = "";
if (onResult)
cbCode += `(function(_err${tapIndex}, _result${tapIndex}) {\n`;
else cbCode += `(function(_err${tapIndex}) {\n`;
cbCode += `if(_err${tapIndex}) {\n`;
cbCode += onError(`_err${tapIndex}`);
cbCode += "} else {\n";
if (onResult) {
cbCode += onResult(`_result${tapIndex}`);
}
if (onDone) {
cbCode += onDone();
}
cbCode += "}\n";
cbCode += "})";
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined,
after: cbCode
})});\n`;
break;
case "promise":
code += `var _hasResult${tapIndex} = false;\n`;
code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
code += `_promise${tapIndex}.then((function(_result${tapIndex}) {\n`;
code += `_hasResult${tapIndex} = true;\n`;
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
code += `}), function(_err${tapIndex}) {\n`;
code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
code += onError(`_err${tapIndex}`);
code += "});\n";
break;
}
return code;
}
复制代码
开头是遍历拦截器,如果有 tap 属性方法,就拼接出它的执行字符串代码。
所以每个订阅函数执行前都会先执行拦截器的 tap 方法。
接着是定义 var _fn${tapIndex} ,其实就是 var _fn0 = _x[0] 这种形式。
然后判断当前的 tap 的 type 类型,进入不同的分支。
这里看一下 sync 分支。
- 如果 rethrowIfPossible 是 false 就拼接 ** _hasError** 和 try
- 根据 onResult 的情况,拼接出是否需要缓存订阅函数的执行结果为 __result,因为有些 Hook 需要根据结果决定是否继续向下执行
- 再次根据 rethrowIfPossible 的情况,拼接 catch 部分,这样 try…catch 就完整了,第2步就是 try 的内容
- 再根据 onResult 的情况,把缓存的执行结果当参数传递,拼接 if…else 内容。比如 SyncBailHook 的 onResult 是:
class SyncBailHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, resultReturns, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onResult: (i, result, next) =>
`if(${result} !== undefined) {\n${onResult(
result
)};\n} else {\n${next()}}\n`,
resultReturns,
onDone,
rethrowIfPossible
});
}
}
复制代码
从这里的 onResult 就可以拼接出决定执行结果不是 undefined 时就不会执行 next 也就是不会向下执行的字符串代码了。
5. 执行 onDone 就是 callTapsSeries 方法中的 current ,它缓存的是上一次订阅函数执行拼接的字符串代码
6. 最后根据 rethrowIfPossible 闭合代码块
这就是整个的拼接流程。
拼接的字符串代码,最终通过 new Function 创建出一个新的函数并赋值给 call。
tapAsync 和 promise 也是按照这个流程进行拼接的,只是具体拼接的代码有所区别。详细的参考分支里的 async 和 promise 吧。
HookMap
HookMap 类就是根据字符串映射到一个 Hook 上。内部就是根据 key 存储在一个 Map 对象中,用的时候根据具体的 key 值就能获取到对应的 Hook。
class HookMap {
constructor(factory, name = undefined) {
this._map = new Map();
this.name = name;
this._factory = factory;
this._interceptors = [];
}
get(key) {
return this._map.get(key);
}
for(key) {
const hook = this.get(key);
if (hook !== undefined) {
return hook;
}
let newHook = this._factory(key);
const interceptors = this._interceptors;
for (let i = 0; i < interceptors.length; i++) {
newHook = interceptors[i].factory(key, newHook);
}
this._map.set(key, newHook);
return newHook;
}
intercept(interceptor) {
this._interceptors.push(
Object.assign(
{
factory: defaultFactory
},
interceptor
)
);
}
}
复制代码
其实就是 Map 的 set 和 get 的操作。
MultiHook
MultiHook 就是定义很多 Hook 存放在数组中,当使用 tap 或 tapAsync 或 tapPromise 时,就遍历数组,并调用每个成员相应的 tap 或 tapAsync 或 tapPromise 方法。
这就是定义一次订阅函数,可以分发到很多 Hook 上。
class MultiHook {
constructor(hooks, name = undefined) {
this.hooks = hooks;
this.name = name;
}
tap(options, fn) {
for (const hook of this.hooks) {
hook.tap(options, fn);
}
}
tapAsync(options, fn) {
for (const hook of this.hooks) {
hook.tapAsync(options, fn);
}
}
tapPromise(options, fn) {
for (const hook of this.hooks) {
hook.tapPromise(options, fn);
}
}
isUsed() {
for (const hook of this.hooks) {
if (hook.isUsed()) return true;
}
return false;
}
intercept(interceptor) {
for (const hook of this.hooks) {
hook.intercept(interceptor);
}
}
withOptions(options) {
return new MultiHook(
this.hooks.map(h => h.withOptions(options)),
this.name
);
}
}
复制代码
结语
Tapable 的源码就分析完了,对于 callAsync 和 promise 方法拼接的过程,感兴趣的可以自己看一下。
整体来看,Tapable 的源码并不复杂,但是它却是支撑起整个 Webpack 插件的核心,里面的思想还是很值得学习的。
更多精彩,请关注微信公众号:闹闹前端