Skip to content

Tapable

tapable 介绍

Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable,Webpack 中最核心的,负责编译的 Compiler 和负责创建 bundles 的 Compilation 都是 tapable 构造函数的实例。

其核心理念是基础发布订阅模式,且基于传统的发布订阅模式添加了很多核心的功能,如:

  • 订阅函数的执行流程,在按照订阅的顺序从前向后依次执行不间断执行得基础上提供了熔断(遇到非 undefined 的返回值则停止后面的执行),瀑布(前面函数的返回值作为后一个函数的入参),循环(如果返回值不是 undefined,则从新从头执行)等流程
  • 订阅函数提供异步的功能,且提供异步并行、串行等方式

打开 Webpack 4.0 的源码中一定会看到下面这些以 Sync、Async 开头,以 Hook 结尾的方法,这些都是 tapable 核心库的类,为我们提供不同的事件流执行机制,我们称为 “钩子”。

例子

在 tapable 中主要使用其提供的事件流执行钩子函数()去创建相应的实例对象,然后调用如 syncHook.tap()去添加队列处理函数,最后通过 SyncHook.call()去执行

js
const { SyncHook } = require("tapable")

// 创建一个同步事件流实例对象
const h1 = new SyncHook(["xxx"])

// 添加同步事件A
h1.tap("A", function (args) {
  console.log("A", args)
  return "b"
})

// 添加同步事件B
h1.tap("B", function () {
  console.log("b")
})

// 添加同步事件C
h1.tap("C", function () {
  console.log("c")
})
// 添加同步事件F
h1.tap(
  {
    name: "F",
    before: "D",
  },
  function () {}
)
// 添加同步事件E
h1.tap(
  {
    name: "E",
    before: "C",
  },
  function () {}
)
// 添加同步事件D
h1.tap("D", function () {
  console.log("d")
})

// 执行这个事件流
h1.call(7777)

分类

tapble 基于上述介绍分别提供了不同的函数方法,按照执行方式可以分为

  • 同步执行
  • 异步执行

按照执行逻辑关系可以分为:

  • 简单顺序执行 从前往后依次执行,不管执行的结果如何
  • 熔断模式(Bail) 从前往后依次执行,但如果遇到一个订阅函数的返回值为非 undefined 时,则停止后面订阅函数的执行。
  • 瀑布流模式(Waterfall) 从前往后依次执行,但如果遇到一个订阅函数的返回值为非 undefined 时,则将当前订阅函数的返回值作为下一个订阅函数的入参。
  • 循环模式(Loop) 从前往后依次执行,但如果遇到一个订阅函数的返回值为非 undefined 时,则重新从头开始执行,直到没有返回值。
// 同步钩子函数
SyncHook          // 最简单的同步顺序执行
SyncBailHook      // 同步熔断执行模式
SyncWaterfallHook // 同步瀑布流执行模式
SyncLoopHook      // 同步循环执行模式


// 异步钩子函数
// - 异步并行
AsyncParallelHook
AsyncParallelBailHook
// - 异步串行
AsyncSeriesHook
AsyncSeriesBailHook
AsyncSeriesWaterfallHook

数据结构

tapable 跟传统发布订阅者模式不一样的地方还有其数据结构的保存和执行方式,一般的发布订阅者模式,其订阅者传入的是一个函数,然后发布时通过 subs 依次执行函数即可,但是 tabpable 将这个流程拆分为两个结构:Hook 和 HookCodeFactory,具体可以分为:

  1. Hook

通过继承的方式提供发布订阅者模式中 订阅(tap) 和 执行(call) 的流程

js
class Hook {
  constructor(args) {
    if (!Array.isArray(args)) args = []
    this._args = args
    this.taps = []
    this.interceptors = []

    // 初始化的时候 通过函数柯里化 去生成 xx.call() 处理函数
    this.call = this._call = this._createCompileDelegate("call", "sync")
    this.promise = this._promise = this._createCompileDelegate("promise", "promise")
    this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "async")
    this._x = undefined
  }

  /**
   * this.call = this._call = this._createCompileDelegate("call", "sync")
   * 执行事件流的函数
   */
  _createCompileDelegate(name, type) {}

  /**
	 * 添加事件流 
	 */
  tap(options, fn) {}
  tapAsync(options, fn) {}
  tapPromise(options, fn) {}

  withOptions(options) {}
  intercept(interceptor) {}
}
  1. HookCodeFactory

跟传统不一样的地方,通过对于不同类型的执行方式生成不同的可执行代码字符串并通过new Function('name,age', '')包裹订阅函数,然后在 call 的流程中去动态执行可执行代码字符串来维护不同的执行流程

例子如下:

js
// new Function('name,age', ``);

;("use strict")
var _context
var _x = this._x
var _taps = this.taps
var _interceptors = this.interceptors
_interceptors[0].call(name, age)
var _tap0 = _taps[0]
_interceptors[0].tap(_tap0)
var _fn0 = _x[0]
var _result0 = _fn0(name, age)
if (_result0 !== undefined) {
  return _result0
} else {
  var _tap1 = _taps[1]
  _interceptors[0].tap(_tap1)
  var _fn1 = _x[1]
  var _result1 = _fn1(name, age)
  if (_result1 !== undefined) {
    return _result1
  } else {
    var _tap2 = _taps[2]
    _interceptors[0].tap(_tap2)
    var _fn2 = _x[2]
    var _result2 = _fn2(name, age)
    if (_result2 !== undefined) {
      return _result2
    } else {
      var _tap3 = _taps[3]
      _interceptors[0].tap(_tap3)
      var _fn3 = _x[3]
      var _result3 = _fn3(name, age)
      if (_result3 !== undefined) {
        return _result3
      } else {
        var _tap4 = _taps[4]
        _interceptors[0].tap(_tap4)
        var _fn4 = _x[4]
        var _result4 = _fn4(name, age)
        if (_result4 !== undefined) {
          return _result4
        } else {
          var _tap5 = _taps[5]
          _interceptors[0].tap(_tap5)
          var _fn5 = _x[5]
          var _result5 = _fn5(name, age)
          if (_result5 !== undefined) {
            return _result5
          } else {
          }
        }
      }
    }
  }
}

上次更新: