Tapable源码分析

295 阅读5分钟

版本:2.2.1
省略了context,interceptors的代码。

基于发布/订阅模式

tap: hook上注册函数
call: 执行注册函数
升级版发布订阅(功能更多,例如:参数列表,拦截器,多种同步/异步钩子,多种函数执行流程)。

const {SyncHook} = require('tapable')
const h1 = new SyncHook(["test"]);
h1.tap("C", (test)=>console.log(test));
h1.call("1");// 打印 1

SyncHook源码


Tapable中的各类Hook,架构是一致的,我从SyncHook 源码举例说明。

// 简化代码,边界处理的代码已删除
function SyncHook(args = [], name = undefined) {
  // 各类Hook的核心 Hook类
  // 负责:tap(注册) call(调用)
	const hook = new Hook(args, name);
  // compile由HookCodeFactory类实现
  // 负责:编译(动态生成hook.call函数) 
	hook.compile = COMPILE;
	return hook;
}
// 通过子类实现多态
// 提高hooks的灵活性,可替换性
class SyncHookCodeFactory extends HookCodeFactory {
  // 生成call函数
	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);
};

先分析Hook类

Hook类

constructo

class Hook {
  constructor(args = [], name = undefined) {
    // 回调中可传参数 
    // eg: new SyncHook(["test", "arg2", "arg3"]);
    // 在执行回调函数中可进行传参
		this._args = args;
		this.name = name;
    // tap注册函数
		this.taps = [];
    // 三种回调类型执行 call
		this.call = CALL_DELEGATE;
		this.callAsync = CALL_ASYNC_DELEGATE;
		this.promise = PROMISE_DELEGATE;
    // 三种注册方式
		this.tap = this.tap;
		this.tapAsync = this.tapAsync;
		this.tapPromise = this.tapPromise;
		this._x = undefined;
	}
}

tab方法

tap方法,是_tap的包装,用于区分回调类型

// options一般是 string
// 就是tap(name,callback)
class Hook{
  tap(options, fn) { 
    this._tap("sync", options, fn);
  }
  tapAsync(options, fn) {
    this._tap("async", options, fn);
  }
  tapPromise(options, fn) {
    this._tap("promise", options, fn);
  }
}

_tap

tap 插入 this.taps

class Hook {
  _tap(type, options, fn) {
    if (typeof options === "string") {
      options = { name: options.trim()};
    }
    options = Object.assign({ type, fn }, options);
    // 拦截器执行register
    options = this._runRegisterInterceptors(options);
    // 将 监听options 插入
    // 可以简单认为 this.taps.push[options]
    this._insert(options);
    // options 值: {type: "sync", fn: Function, name: "A"}
  }
}

tap 流程结束。

call方法

创建call, 执行 call(...args)

// 在Hook的constructor中  call被赋值
// 3种call调用方式
class Hook{
  constructor(args = [], name = undefined) {
      this.call = CALL_DELEGATE;
      this.callAsync = CALL_ASYNC_DELEGATE;
      this.promise = PROMISE_DELEGATE;
  }
}
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);
};

3种调用都是_createCall的包装。

_createCall

调用 this.compile,必须有子类重写这个方法。

class Hook{
	_createCall(type) {
		return this.compile({
			taps: this.taps,
			interceptors: this.interceptors,
			args: this._args,
			type: type
		});
	}
	
  compile(options) {
		throw new Error("Abstract: should be overridden");
	}
}

compile

SyncHook的举例

// 简化 按思考顺序排列代码
function SyncHook(args = [], name = undefined) {
  const hook = new Hook(args, name);
  // 重写compile
	hook.compile = COMPILE;
	return hook;
}

// 先知道会动态返回 call函数(功能:执行tap注册函数)即可。 下面细讲 
const COMPILE = function(options) {
	factory.setup(this, options);
	return factory.create(options);
};

const factory = new SyncHookCodeFactory();

// compile都是由HookCodeFactory的子类完成
class SyncHookCodeFactory extends HookCodeFactory {
  // 处理compile的call函数内容,按照什么逻辑构建.
	content({ onError, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}

Hook.call流程图

Hook.call流程图.jpg
下面我们来研究HookCodeFactory的源码,如何动态生成call

HookCodeFactory类

看名字就知道,这个生成code的。
功能:遍历taps,根据回调类型,执行逻辑,生成不同的call函数。(拼接字符串 new Function

从SyncHook的compile入手

const COMPILE = function(options) {
	factory.setup(this, options);
	return factory.create(options);
};

从setup出发。

setup

tap注册的函数全部取出。

class HookCodeFactory {
  setup(instance, options) {
    // taps中的函数保存到 _x 中。
		instance._x = options.taps.map(t => t.fn);
	}
}

create

动态构建call函数。
我们只关注 type:'sync' 的。

class HookCodeFactory {
  // 初始化
  init(options) {
		this.options = options;
    // 获取args eg: new SyncHook(["test", "arg2", "arg3"]);
    // args 就是 ["test", "arg2", "arg3"]
		this._args = options.args.slice();
	}
  // 创建
	create(options) {
		this.init(options);
    
		let fn;
    // 区分回调类型 
    // 通过拼接字符串 构建 call函数
    // 在下面详细讲这个拼接
		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;
	}
}

拼接字符串 生成 call

type == 'sync' 的部分详细讲一下。

// 全部示例都为 new SyncHook(["test", "arg2", "arg3"])  
// 忽略context interceptors .

// (step 0)
fn = new Function(
  this.args(), // 'test, arg2, arg3'
  '"use strict";\n' +
  this.header() + // var _x = this._x;
  this.contentWithInterceptors({ // var _fn0 = _x[0];_fn0(test, arg2, arg3);
    onError: err => `throw ${err};\n`,
    onResult: result => `return ${result};\n`,
    resultReturns: true,
    onDone: () => "",
    rethrowIfPossible: true
  })
);
// fn最终函数为如下:
// function anonymous(test, arg2, arg3) {
// 	"use strict";
// 	var _context;
// 	var _x = this._x;
// 	var _fn0 = _x[0];
// 	_fn0(test, arg2, arg3);
// }

// 简化后代码
class HookCodeFactory {
  
  // (step 1)
  // 将args转换为 args 字符串  
  // eg: new SyncHook(["test", "arg2", "arg3"]);输出 'test, arg2, arg3'
	args(){
    let allArgs = this._args;
    return  allArgs.length === 0 ? "":allArgs.join(", ");
  }
  
  // (step 2)
  // 代码顶部变量声明
  // 主要将 _x(taps所有注册函数)放入函数。 
  header() {
		let code = "";
    // 省略context代码
    code += "var _context;\n";
    // 将 _x 放入字符串
		code += "var _x = this._x;\n";
    // 省略interceptors代码
		return code;
	}
  
  // (step 3)   下一步在最下面的子类中
  // 添加拦截器(本文不讨论)在执行content
  contentWithInterceptors(options) {
    // 省略interceptors代码
    // 注意:content必须有子类重写
    // 在最下面有重写的方法
    return this.content(options);
	}
  
  // (step 3.2)
  // 将sync的taps遍历合成为 执行tap注册函数的,JS代码字符串。
  callTapsSeries({
		onError,
		onResult,
		resultReturns,
		onDone,
		doneReturns,
		rethrowIfPossible
	}) {
		if (this.options.taps.length === 0) return onDone();
		let code = "";
		let current = onDone;
		for (let j = this.options.taps.length - 1; j >= 0; j--) {
			const i = j;
			const done = current;
      // (step 3.3)
      // this.callTap:将单个tap构建成 JS代码字符串
			const content = this.callTap(i, {
				onError: error => onError(i, error, done),
				onResult:result => onResult(i, result, done),
        onDone: !onResult && done,
			});
			current = () => content;
		}
		code += current();
    // 举例 code: var _fn0 = _x[0];_fn0(test, arg2, arg3);
		return code;
    // 返回 step( 0 )
	}
  
  // (step 3.3)
  // 将tap注册函数 转换为js代码字符串,
  callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
		let code = "";
		// 省略interceptors的代码
    // 将数组展开,举例 code: var _fn0 = _x[0]
		code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
		const tap = this.options.taps[tapIndex];
		switch (tap.type) {
			case "sync":
        code += `_fn${tapIndex}(${this.args()});\n`;
        // 举例 code: var _fn0 = _x[0];_fn0(test, arg2, arg3);
				if (onDone) {
					code += onDone(); // onDone将所有tap字符串连接起来. 
				}
				break;
			case "async":
				// 略过,async类的代码 逻辑也是生成字符串
				break;
			case "promise":
				// 略过,promise类的代码 逻辑也是生成字符串
				break;
		}
    // 举例 code: var _fn0 = _x[0];_fn0(test, arg2, arg3);
		return code;
	}
}

// 子类重写的content方法
class SyncHookCodeFactory extends HookCodeFactory {
  // (step 3.1)
  // content方法
	content({ onError, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}

HookCodeFactory流程图

[tapable]HookCodeFactory.drawio.drawio (3).png

Tapable架构图

tapable.png

总结

  • Hook:负责订阅发布。
    • tap(注册函数),call(执行)。
  • HookCodeFactory:负责编译,动态生成 供 Hook 使用的call 函数。
    • 区分回调类型syncasyncpromise。 内部compile函数自行区分。
    • 区分执行流程:继承的子类,**重写 **content,分流程调用
      • 串行 synccallTapsSeries
      • 循环 loopcallTapsLooping
      • 并行 parallelcallTapsParallel


(各类Hook):内聚上述逻辑,实现不同的执行逻辑和回调类型。

如何实现灵活的订阅发布?

  • 一个类实现 订阅发布。
  • 一个类实现 灵活。
  • 然后将2个类进行组合,完善具体逻辑。