Tapable的神秘之处-源码解析(2)
前言:研究webpack过程中,发现tapable在其中占据了很重要的成分,所以就看看它的奥妙之处
Tapable是一个在webpack中被广泛使用的核心模块,它提供了一组灵活的钩子函数,可以在不同的生命周期中插入自定义的逻辑。通过使用Tapable,我们可以轻松地实现各种功能,从简单的插件拓展到复杂的编译过程优化。在这篇文章中,我们将深入探索Tapable的神秘之处,了解它的底层实现。
源码目录结构
tapable
├─ .babelrc
├─ .editorconfig
├─ .eslintrc
├─ .gitattributes
├─ .gitignore
├─ .prettierrc.js
├─ .travis.yml
├─ README.md
├─ lib
│ ├─ AsyncParallelBailHook.js
│ ├─ AsyncParallelHook.js
│ ├─ AsyncSeriesBailHook.js
│ ├─ AsyncSeriesHook.js
│ ├─ AsyncSeriesLoopHook.js
│ ├─ AsyncSeriesWaterfallHook.js
│ ├─ Hook.js
│ ├─ HookCodeFactory.js
│ ├─ HookMap.js
│ ├─ index.js
│ ├─ MultiHook.js
│ ├─ SyncBailHook.js
│ ├─ SyncHook.js
│ ├─ SyncLoopHook.js
│ ├─ SyncWaterfallHook.js
│ ├─ util-browser.js
│ └─ __tests__
│ ├─ AsyncParallelHooks.js
│ ├─ AsyncSeriesHooks.js
│ ├─ Hook.js
│ ├─ HookCodeFactory.js
│ ├─ HookStackOverflow.js
│ ├─ HookTester.js
│ ├─ MultiHook.js
│ ├─ SyncBailHook.js
│ ├─ SyncHook.js
│ ├─ SyncHooks.js
│ ├─ SyncWaterfallHook.js
│ └─ __snapshots__
│ ├─ AsyncParallelHooks.js.snap
│ ├─ AsyncSeriesHooks.js.snap
│ ├─ HookCodeFactory.js.snap
│ └─ SyncHooks.js.snap
├─ LICENSE
├─ package.json
├─ tapable.d.ts
└─ yarn.lock
书接上文的Tapable的神秘之处-源码解析(1),今天我们继续来看看lib目录中的其他文件
工具方法lib/HookMap.js
代码里定义个一个HookMap类的构造函数 接受两个可选参数:factory(工厂函数)和name(HookMap的实例名称)。在构造函数中,初始化了一些实例属性,包括_map、name、_factory、_interceptors等,并设置了默认的调用方法(call、callAsync、promise)
初始化配置部分
constructor(factory, name = undefined) {
// 内部映射用于存储和管理钩子信息
this._map = new Map();
// hookMap实例名称标识,用于后续引用。
this.name = name;
// 传入的工厂函数 factory 赋值给 this._factory,这样以后就可以用来创建新的钩子。
this._factory = factory;
// 用于存储拦截器,拦截器可以在钩子创建过程中修改它们或者在钩子触发事件之前做一些操作
this._interceptors = [];
}
HookMap.prototype.tap = util.deprecate(function(key, options, fn) {
return this.for(key).tap(options, fn);
}, "HookMap#tap(key,…) is deprecated. Use HookMap#for(key).tap(…) instead.");
HookMap.prototype.tapAsync = util.deprecate(function(key, options, fn) {
return this.for(key).tapAsync(options, fn);
}, "HookMap#tapAsync(key,…) is deprecated. Use HookMap#for(key).tapAsync(…) instead.");
HookMap.prototype.tapPromise = util.deprecate(function(key, options, fn) {
return this.for(key).tapPromise(options, fn);
}, "HookMap#tapPromise(key,…) is deprecated. Use HookMap#for(key).tapPromise(…) instead.");
get(key)方法和for(key)方法
get(key) {
return this._map.get(key);
}
for(key) {
// 寻找this._map里面是否存在对应key的钩子,如果寻找的到,直接就返回现有的钩子
const hook = this.get(key);
if (hook !== undefined) {
return hook;
}
// 调用_factory函数创建一个新的钩子(按需创建)
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;
}
-
get(key)方法: 这个方法接受一个键(key)作为参数,并尝试从HookMap实例的内部映射(_map)中检索与这个键相关联的钩子。如果找到了这个钩子,它就会被返回;如果没有找到,方法会返回undefined。 -
for(key)方法: 这个方法也接受一个键(key)作为参数,目的是要确保与这个键相关联的钩子存在。如果内部映射中已经有了对应的钩子,它就会直接返回这个现有的钩子。如果找不到,方法将执行以下步骤:
- 调用
_factory函数创建一个新的钩子。这个工厂函数是在HookMap构造时传入的,用于按需创建钩子。 - 遍历
_interceptors数组,这个数组包含了所有的拦截器。拦截器是在钩子创建过程中可以介入的对象,每个拦截器都有可能修改新创建的钩子。 - 对每个拦截器调用
factory方法,传入当前的键和新创建的钩子。这允许拦截器根据键和钩子的当前状态来修改钩子。 - 将新创建(并可能被拦截器修改过的)钩子与键关联并存储到内部映射中。
- 返回新创建的钩子。
- 调用
这样,for方法确保无论何时请求特定的键,都会得到一个钩子,如果这个钩子不存在,就现场创建一个。这允许HookMap按需动态地管理钩子,而无需预先定义所有可能的钩子。
intercept(interceptor)方法
intercept(interceptor) {
this._interceptors.push(
Object.assign(
{
factory: defaultFactory
},
interceptor
)
);
}
该方法的作用是向 HookMap 实例的内部拦截器数组 _interceptors 添加一个新的拦截器对象
结尾
这个 HookMap 类为 tapable 提供了以下功能和特性:
- 管理多个钩子实例:
HookMap管理多个不同类型的钩子实例。它提供了一种集中管理和访问钩子的机制,可以方便地对多个钩子进行操作和触发。 - 提供钩子的注册和访问接口:
HookMap提供了钩子的注册和访问接口。通过注册接口,可以将钩子实例添加到HookMap中,并指定一个标识符来区分不同的钩子。通过访问接口,可以根据标识符获取对应的钩子实例,方便进行后续的操作。 - 支持批量操作:
HookMap支持批量操作多个钩子实例。可以一次性触发或执行多个钩子,避免了对每个钩子实例进行独立操作的麻烦。 - 提供钩子的生命周期管理:
HookMap可以跟踪和管理钩子的生命周期。它可以在钩子注册或注销时执行相应的生命周期钩子函数,用于进行一些额外的处理或清理操作。
综上所述,HookMap在tapable中的作用和意义是提供了一个集中管理和操作多个钩子实例的机制,方便进行批量操作和生命周期管理。它是实现插件系统和事件机制的重要组成部分。