Tapable部分hook源码实现分析
分类:javascript
介绍
Webpack如何和各个插件配合实现打包的?
主要就是借助于 tapable
这个库。
下面我参考 tapable
的源码,简单实现其中的部分 hook
.
SyncHook 同步勾子
使用示例
const SyncHook = require("./lib/syncHook").default;
const hook = new SyncHook(["name", "age"]);
hook.tap("fn1", (name, age) => {
console.log("fn1->>>", name, age);
});
hook.tap("fn2", (name, age) => {
console.log("fn2->>>", name, age);
});
hook.call("kww", 16);
// 控制台打印
fn1->>> kww 16
fn2->>> kww 16
源码实现
抽象基类 Hook
我先简单实现一个抽象类 Hook,tapable中的syncHook、asyncParallelHook等hook都继承于这个 Hook。
/**
tap 结构
{
name: string;
fn: Function;
type: 'sync' | 'async;
}
*/
abstract class Hook {
// 形式参数列表, 如 new SyncHook(["name", "age"]) 中的 ["name", "age"]
public args: string[];
// 注册的订阅对象列表
public taps: object[];
// 订阅对象的 callback 函数列表
protected _x: Function[];
constructor(args = []) {
this.args = args;
this.taps = [];
this._x = undefined;
}
// hook.tap('fn1', () => {})
tap(options, fn) {
if (typeof options === "string") {
options = {
name: options,
};
}
// tap = { fn: f(){}, name: "fn1" }
const tap = Object.assign(
{
// 源码里tap对象还有 type: "sync",以及 拦截器 interceptors,我这里就省略了
fn,
},
options
);
this._insert(tap);
}
// 注册 tap
private _insert(tap) {
this.taps[this.taps.length] = tap; // this.taps.push(tap);
}
call(...args) {
// 创建将来要具体执行的函数代码结构
let callFn = this._createCall();
// 调用上述的函数
return callFn.apply(this, args);
}
private _createCall(type) {
return this.compile({
taps: this.taps,
args: this.args,
type,
});
}
abstract compile(data: { taps: any[]; args: string[]; type: "sync" | "async"|"promise" }): Function;
}
export default Hook;
实现子类 SyncHook
最终的执行函数,是通过不同的codeFactory动态生成出来的。先实现 SyncHookCodeFactory
,再实现 SyncHook
。
import Hook from "./hook";
// 源码中 SyncHookCodeFactory 还继承于名为 HookCodeFactory 的基类工厂
// 这里就简化一下直接写到这里了
class SyncHookCodeFactory {
public options: {
taps: any[]; // [{ name:'fn1', fn: f(){} }, { name:'fn2', fn: f(){} },]
args: string[]; // ["name", "age"]
};
setup(instance, options) {
// 这里的操作在源码中是通过 init 方法实现的
this.options = options;
// 获取所有注册的tap的回调函数
instance._x = options.taps.map((o) => o.fn);
}
/**
* 创建一段可以执行的代码题,然后返回
*
* 源码中是写在基类工厂中的,通过switch(this.options.type) 执行不同的逻辑,
* this.options.type 就是 sync、async、promise
* @param options
*/
create(options) {
let fn;
// 使用 new Function 将字符串转成函数
fn = new Function(
// ["name", "age"] 转成 "name,age",作为生成的函数的形式参数, 如 f(name, age) {...}
this.args(),
`
${this.head()}
${this.content()}
`
);
return fn;
}
// 基类中固定的方法
head() {
return "var _x = this._x;";
}
// 子类实现方法,生成类似下面的字符串
//
// var _fn0 = _x[0]; _x 是注册的所有tap的回调函数
// _fn0(name, age);
//
content() {
let code = "";
for (var i = 0; i < this.options.taps.length; i++) {
code += `
var _fn${i} = _x[${i}];
_fn${i}(${this.args()});
`;
}
return code;
}
args() {
return this.options.args.join(",");
}
}
let factory = new SyncHookCodeFactory();
class SyncHook extends Hook {
constructor(args) {
super(args);
}
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
export default SyncHook;
AsyncParallelHook 异步并行勾子
使用示例
const AsyncParallelHook = require("./src/asyncParallelHook").default;
const hook = new AsyncParallelHook(["name", "age"]);
console.time('time')
hook.tapAsync("fn1", (name, age, callback) => {
console.log("fn1->>>", name, age);
setTimeout(()=>{
callback();
},1000)
});
hook.tapAsync("fn2", (name, age, callback) => {
console.log("fn2->>>", name, age);
setTimeout(()=>{
callback();
},2000)
});
hook.callAsync("kww", 16, function () {
console.log("end");
console.timeEnd('time')
});
// 终端打印
fn1->>> kww 16
fn2->>> kww 16
end
time: 2009.890ms
源码实现
实现子类 AsyncParallelHook
AsyncParallelHook
和 SyncHook
的没有太大区别,主要是各自的 codeFactory有些差异。
class AsyncParallelHookCodeFactory {
...
create(options) {
let fn;
// f(name,age, _callback){...}
fn = new Function(
this.args({ after: "_callback" }), // "name,age,_callback"
`
${this.head()}
${this.content()}
`
);
return fn;
}
// 生成类似如下代码
// var _counter = 2;
// var _done = function(){_callback();};
//
// var _fn0 = _x[0];
// _fn0(name, age, function() {
// if(--_counter === 0) _done();
// });
//
// var _fn1 = _x[1];
// _fn1(name, age, function() {
// if(--_counter === 0) _done();
// });
//
content() {
let code = `var _counter = ${this.options.taps.length};
var _done = function(){_callback();};`;
for (var i = 0; i < this.options.taps.length; i++) {
code += `
var _fn${i} = _x[${i}];
_fn${i}(${this.args()}, function(){
if(--_counter === 0) _done();
});
`;
}
return code;
}
args(data?: { before?: string; after?: string }) {
let args = this.options.args.slice();
//if (data?.before) {
// args = [data.before].concat(args);
//}
if (data?.after) {
args = args.concat([data.after]); // ["name", "age", "_callback"]
}
return args.join(",");
}
...
}
var factory = new AsyncParallelHookCodeFactory();
class AsyncParallelHook extends Hook {
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
一线大厂高级前端编写,前端初中阶面试题,帮助初学者应聘,需要联系微信:javadudu