webpack作为前端打包的重要工具之一,我们有必要深入掌握webpack的底层数据结构。官方文档上直接给出了webpack的核心模块tapable,但是并没有给出特别详细的介绍其实现原理和内部实现,本文从源码触发,来解析下tapable的数据结构和方法属性,这对于掌握webpack打包机制的理解是很有帮助的。
总览:
hook.js数据结构:
constructor(args) {
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.interceptors = [];
this.call = this._call;
this.promise = this._promise;
this.callAsync = this._callAsync;
this._x = undefined;
}
两个集合,taps集合,interceptors集合,并定义了三个操作tap的方法tap, tapAsync, tapPromise,对应的还有三个创建委托函数(将监听函数转化为字符串)的方法_call, _promise, _callAsync
HookCodeFactory
各种钩子的工厂类基础类,每个钩子的工厂类会继承HookCodeFactory。比如syncHook
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();
class SyncHook extends Hook {
tapAsync() {
throw new Error("tapAsync is not supported on a SyncHook");
}
tapPromise() {
throw new Error("tapPromise is not supported on a SyncHook");
}
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
module.exports = SyncHook;
说明:各种钩子继承Hook类之后,会定义compile方法,该方法调用了工厂类实例,最终和HookCodeFactory联系起来,在各种钩子上去执行HookCodeFactory的方法
HookCodeFactory的方法:
主要实现了串行,并行,循环,执行单个监听函数的方法,也就是taps出栈的方法
HookMap的数据结构
constructor(factory) {
this._map = new Map();
this._factory = factory;
this._interceptors = [];
}
可以看出,在hookMap内定义了字典的数据结构来存储hook实例
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;
}
for方法将传入的factory的hook设置到字典
MultiHook
constructor(hooks) {
this.hooks = hooks;
}
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);
}
}
批量操作hooks
其余钩子
其余的钩子函数都具有类似性,根据串行,并行或者其他在钩子工厂实例的content方法中执行不同的HookCodeFactory方法
比如:
总结:
同步钩子:
- SyncBailHook:类似于 SyncHook,执行过程中注册的回调返回非 undefined 时就停止不在执行。
- SyncWaterfallHook:接受至少一个参数,上一个注册的回调返回值会作为下一个注册的回调的参数。
- SyncLoopHook:有点类似 SyncBailHook,但是是在执行过程中回调返回非 undefined 时继续再次执行当前的回调。
异步钩子与之相同,这里不再阐述具体实现原理。