Tapable的神秘之处-源码解析(1)
前言:研究webpack过程中,发现tapable在其中占据了很重要的成分,所以就看看它的奥妙之处
Tapable是一个在webpack中被广泛使用的核心模块,它提供了一组灵活的钩子函数,可以在不同的生命周期中插入自定义的逻辑。通过使用Tapable,我们可以轻松地实现各种功能,从简单的插件拓展到复杂的编译过程优化。在这篇文章中,我们将深入探索Tapable的神秘之处,了解它的底层实现。
从官方文档可以看到tapable提供以下方法
1  | js复制代码const {  | 
为了弄清楚这些方法都是有什么秘密可言,让我们开始从tapable仓库开始探究吧。
源码目录结构
1  | markdown复制代码tapable  | 
可以看出tapable的源码方法都是放置在lib文件夹中,lib/index.js就是tapable入口文件,并且除了10个对应的Hook文件,还有其他几个方法文件。
今天我们先来看看 Hook.js这个文件到底做了那些有意义的事
核心实现方法lib/Hook.js
代码里定义个一个Hook类的构造函数 接受两个可选参数:args(参数数组)和name(Hook的名称)。在构造函数中,初始化了一些实例属性,包括_args、name、taps、interceptors等,并设置了默认的调用方法(call、callAsync、promise)
1  | js复制代码初始化配置部分  | 
这段代码定义了一个注册机制,支持不同类型的注册方式(同步、异步、基于Promise的异步),通过对外提供的 tap, tapAsync, 和 tapPromise 方法来实现。它主要用于事件或插件系统中,允许开发者根据不同需求注册回调函数。
_tap方法
1  | js复制代码_tap(type, options, fn) {  | 
_tap(type, options, fn)方法是这个注册机制的内核。- 参数 
type指明了注册的类型,可以是"sync","async", 或"promise"。 - 参数 
options可以是一个字符串或一个对象。如果是字符串,将被转换成一个对象,对象中有一个name属性。如果是一个对象,它必须至少包含一个名为name的字符串属性。options还可以包含一个可选的context属性,若存在,则调用deprecateContext()函数(可能用于标记该属性为过时)。 - 参数 
fn是一个将被注册的函数。 - 方法内部,首先检查 
options参数的有效性,然后将type和fn加入到options对象中,最后通过_runRegisterInterceptors方法处理options,并通过_insert方法将处理后的options插入到注册列表中。 
tap, tapAsync, tapPromise 方法:
这三个方法是 _tap 方法的封装,分别对应不同的 type 参数:
tap(options, fn)用于注册同步类型的回调。tapAsync(options, fn)用于注册异步类型的回调。tapPromise(options, fn)用于注册基于Promise的异步回调。
使用场景:
这种模式广泛用于插件系统和事件处理系统,例如Webpack的插件系统就是一个典型的例子。通过这种方式,开发者能够以灵活的方式扩展或修改应用程序的行为,同时保持代码的整洁和组织。每种类型的回调提供了不同的处理方式,以适应不同的执行环境和需求:
- 使用 
tap注册的回调将同步执行,适用于执行时间短、不涉及IO操作的场景。 - 使用 
tapAsync注册的回调将异步执行,但需要回调函数来通知执行完成,适用于涉及IO操作等可能需要等待的场景。 - 使用 
tapPromise注册的回调期望返回一个Promise,适用于现代异步处理,特别是在使用async/await语法时。 
_runRegisterInterceptors方法
1  | js复制代码_runRegisterInterceptors(options) {  | 
这是一个内部方法,用于在注册钩子之前对options对象进行拦截器注册配置。
_runRegisterInterceptors(options)用于在注册钩子之前对options对象进行拦截器注册。。- 参数 
options注册的钩子对象。 - 方法内部,对于
this.interceptors数组中的每个拦截器,检查interceptor.register是否存在。如果存在,调用interceptor.register方法,并传入options对象。如果interceptor.register方法返回一个新的选项对象newOptions,更新options为newOptions。最后返回更新后的options对象。 
_runRegisterInterceptors提供了一种扩展和定制回调函数的机制,通过钩子注册和拦截器,可以灵活地控制和改变回调函数的行为
withOptions方法
1  | js复制代码withOptions(options) {  | 
是tapable用于创建一个新的含有特定选项的对象的公共方法
isUsed方法
1  | js复制代码isUsed() {  | 
用于检查钩子是否被使用。
intercept方法
1  | js复制代码intercept(interceptor) {  | 
用于注册一个拦截器,并同时将拦截器对象添加到钩子对象的 taps 数组中。
_resetCompilation方法
1  | js复制代码_resetCompilation() {  | 
用于重置钩子对象的编译状态。
_insert方法
1  | js复制代码_insert(item) {  | 
解析 item.before 和 item.stage
- 如果 
item.before是字符串类型,将其作为一个元素放入一个新的集合before中。 - 如果 
item.before是数组类型,将其作为多个元素放入一个新的集合before中。 - 如果 
item.stage是数字类型,将其赋值给stage。 
从钩子对象的回调函数列表的末尾开始遍历,直到找到合适的插入位置
该方法的目的是根据给定的条件将新的回调函数插入到钩子对象的回调函数列表中。通过调整回调函数的顺序,可以控制它们的执行顺序和优先级。
结尾
这个 Hook 类为 tapable 提供了以下功能和特性:
- 注册和执行回调函数:
Hook类提供了tap、tapAsync和tapPromise方法,用于注册回调函数并在适当的时机执行这些回调函数。 - 参数配置:可以在 
Hook类的构造函数中传入参数配置数组,用于在执行回调函数时传递参数。 - 拦截器:
Hook类支持注册拦截器对象,并在需要时调用拦截器的register方法对已注册的回调函数进行修改或替换。 - 调用委托函数:
Hook类定义了_call、call、_callAsync、callAsync、_promise和promise等委托函数,用于委托具体的调用行为。 - 钩子编译:
Hook类的compile方法可以被派生类重写,用于提供具体的编译逻辑,生成最终的调用函数。 - 钩子选项:
Hook类提供了withOptions方法,用于将给定的选项与原始选项进行合并,并返回一个新的对象,该对象具有与原始Hook实例相同的方法,但使用合并后的选项。 - 基本信息:
Hook类包含一些基本的信息,如名称(name)和注册的回调函数(taps)。 - 可用性检查:
Hook类提供了isUsed方法,用于判断当前Hook实例是否被使用,即是否注册了回调函数或拦截器对象。 
通过这些功能和特性,Hook 类为 tapable 提供了一种机制,用于在不同的时机执行注册的回调函数,并支持对回调函数进行拦截和修改。这为插件系统、事件系统和钩子机制提供了基础框架和工具。
本文转载自: 掘金