{T}

二、tapable 库介绍

2.1 基本信息

项目内容
组织webpack 组织
用途为插件系统提供钩子机制
GitHubhttps://github.com/webpack/tapable

2.2 提供的方法分类

code
tapable 导出的 Hook 类型:
├── 同步 Hooks(Sync)
│   ├── SyncHook          - 基础同步钩子
│   ├── SyncBailHook      - 保释金钩子(可截断)
│   ├── SyncWaterfallHook - 瀑布流钩子
│   └── SyncLoopHook      - 循环钩子
│
└── 异步 Hooks(Async)
    ├── AsyncSeriesHook   - 异步串行钩子
    ├── AsyncParallelHook - 异步并行钩子
    └── ... 更多异步类型

三、Hook 类型详解

3.1 Basic(基础类型)

特点: 简单执行,每个 tap 的函数都会执行。

code
执行流程:

┌─────────┐     ┌─────────┐     ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘     └─────────┘     └─────────┘
    │               │               │
    ▼               ▼               ▼
  执行            执行            执行

特点:依次执行,互不影响

代码示例:

javascript
const { SyncHook } = require('tapable');

const hook = new SyncHook(['arg1']);

// 注册插件
hook.tap('PluginA', (arg1) => {
  console.log('PluginA:', arg1);
});

hook.tap('PluginB', (arg1) => {
  console.log('PluginB:', arg1);
});

// 调用
hook.call('hello');
// 输出:
// PluginA: hello
// PluginB: hello

3.2 Waterfall(瀑布流类型)

特点: 上一个函数的返回值传递给下一个函数。

code
执行流程:

┌─────────┐     ┌─────────┐     ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘     └─────────┘     └─────────┘
    │               │               │
    ▼               ▼               ▼
  return 1      接收 1          接收 2
                return 2        return 3

特点:返回值像瀑布一样向下传递

代码示例:

javascript
const { SyncWaterfallHook } = require('tapable');

const hook = new SyncWaterfallHook(['value']);

hook.tap('PluginA', (value) => {
  console.log('A:', value);
  return value + 1;  // 返回值传递给下一个
});

hook.tap('PluginB', (value) => {
  console.log('B:', value);  // 接收 A 的返回值
  return value + 1;
});

hook.call(0);
// 输出:
// A: 0
// B: 1

3.3 Bail(保释金/跳板类型)

特点: 任一函数返回非 undefined 值,立即停止后续执行。

code
执行流程:

┌─────────┐     ┌─────────┐     ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘     └─────────┘     └─────────┘
    │               │
    ▼               ▼
  return          return 'stop'
  undefined       (非 undefined)
    │               │
    ▼               ▼
  继续执行        停止!C 不执行

特点:可截断,类似电路中的保险丝

代码示例:

javascript
const { SyncBailHook } = require('tapable');

const hook = new SyncBailHook(['value']);

hook.tap('PluginA', (value) => {
  console.log('A:', value);
  return undefined;  // 继续
});

hook.tap('PluginB', (value) => {
  console.log('B:', value);
  return 'stop';  // 截断!
});

hook.tap('PluginC', (value) => {
  console.log('C:', value);  // 不会执行
});

hook.call('test');
// 输出:
// A: test
// B: test
// C 不会执行

3.4 Loop(循环类型)

特点: 返回非 undefined 值时,从第一个重新开始。

code
执行流程:

第一轮:A → B → C(C 返回非 undefined)
                │
                ▼ 重新开始
第二轮:A → B → C(C 返回 undefined)
                │
                ▼ 结束
           流程完成

特点:循环执行直到所有函数都返回 undefined

代码示例:

javascript
const { SyncLoopHook } = require('tapable');

let count = 0;

const hook = new SyncLoopHook([]);

hook.tap('PluginA', () => {
  console.log('A');
  return undefined;
});

hook.tap('PluginB', () => {
  console.log('B');
  count++;
  if (count < 3) {
    return 'continue';  // 触发重新开始
  }
  return undefined;  // 结束循环
});

hook.call();
// 输出:A B A B A B(循环 3 次)

四、执行机制详解

4.1 同步执行机制

code
┌─────────────────────────────────────────────┐
│              同步 Hook                       │
├─────────────────────────────────────────────┤
│                                             │
│   注册:hook.tap(name, callback)            │
│                                             │
│   调用:hook.call(...args)                  │
│                                             │
│   特点:                                    │
│   - 依次调用,等待上一个完成                │
│   - 同步阻塞执行                            │
│                                             │
└─────────────────────────────────────────────┘

4.2 异步执行机制

异步分为串行(Series)和并行(Parallel):

异步串行(AsyncSeries)

code
┌─────────┐     ┌─────────┐     ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘     └─────────┘     └─────────┘
    │               │               │
    ▼               ▼               ▼
  callback        callback        callback
  完成 →          完成 →          完成

特点:依次执行,等待上一个完成后才执行下一个

异步并行(AsyncParallel)

code
    ┌─────────┐
    │ Plugin A │ ────►
    └─────────┘
         │
┌────────┴────────┐
│                 │
    ┌─────────┐       ┌─────────┐
    │ Plugin B │ ────► │ Plugin C │ ────►
    └─────────┘       └─────────┘
         │                 │
         └────────┬────────┘
                  ▼
             全部完成 → callback

特点:同时执行,等待全部完成后回调

五、注册与调用方法对照表

5.1 方法对照

注册方法调用方法说明
hook.tap(name, callback)hook.call(...args)同步注册与调用
hook.tapAsync(name, callback)hook.callAsync(...args, callback)异步回调式注册与调用
hook.tapPromise(name, callback)hook.promise(...args)Promise 式注册与调用

5.2 详细用法

同步方式

javascript
const { SyncHook } = require('tapable');
const hook = new SyncHook(['arg1', 'arg2']);

// 注册
hook.tap('MyPlugin', (arg1, arg2) => {
  console.log(arg1, arg2);
});

// 调用
hook.call('hello', 'world');

异步回调方式

javascript
const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['arg1']);

// 注册(注意 callback 参数)
hook.tapAsync('MyPlugin', (arg1, callback) => {
  setTimeout(() => {
    console.log(arg1);
    callback();  // 必须调用 callback 表示完成
  }, 1000);
});

// 调用
hook.callAsync('hello', () => {
  console.log('全部完成');
});

Promise 方式

javascript
const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['arg1']);

// 注册(返回 Promise)
hook.tapPromise('MyPlugin', (arg1) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(arg1);
      resolve();
    }, 1000);
  });
});

// 调用
hook.promise('hello').then(() => {
  console.log('全部完成');
});

六、拦截器(Interception)

6.1 拦截器介绍

作用: 在 Hook 执行过程中插入观察/处理逻辑。

javascript
hook.intercept({
  call: (...args) => {
    // 调用时触发
    console.log('调用参数:', args);
  },
  tap: (tap) => {
    // 注册时触发(观察)
    console.log('注册插件:', tap.name);
  },
  loop: (...args) => {
    // Loop 类型每轮开始时触发
    console.log('新一轮循环');
  },
  register: (tap) => {
    // 注册时触发(可修改 tap 对象)
    tap.name = 'Modified:' + tap.name;
    return tap;  // 返回修改后的对象
  }
});

6.2 tap vs register 区别

属性tapregister
触发时机注册时注册时
入参tap 对象tap 对象
返回值voidtap 对象
能否修改 tap不能可以
典型用途观察流程、日志修改 tap 对象

使用建议:

如果不需要修改 tap 对象,优先使用 tap 属性进行观察。


七、核心知识总结

7.1 知识架构图

code
┌─────────────────────────────────────────────────────────────┐
│                    tapable 核心知识                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 Hook 类型分类                        │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  Basic     │ 依次执行,互不影响                      │   │
│  │  Waterfall │ 返回值传递给下一个函数                  │   │
│  │  Bail      │ 非 undefined 返回值截断后续执行        │   │
│  │  Loop      │ 非 undefined 返回值触发重新开始        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   执行机制                          │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  同步      │ tap + call                             │   │
│  │  异步串行  │ tapAsync + callAsync / tapPromise + promise │
│  │  异步并行  │ 多个同时执行,等待全部完成              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                  拦截器(Intercept)                │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  call     │ 调用时触发                              │   │
│  │  tap      │ 注册时观察                              │   │
│  │  loop     │ 循环每轮开始时触发                      │   │
│  │  register │ 注册时可修改 tap 对象                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

7.2 Hook 类型执行对比

code
Basic:
[A] → [B] → [C]  (依次执行)

Waterfall:
[A] → [B] → [C]
 ↓     ↓     ↓
ret→  ret→  ret
      传给下个

Bail:
[A] → [B] → [C]
 ↓     ↓
ret   ret 'stop' ← 截断,C 不执行

Loop:
[A] → [B] → [C] → 返回非 undefined → [A] → [B] → [C] → 返回 undefined → 结束
                  ↑___________________________________|

7.3 必记要点

类别要点
Basic依次执行,互不干扰
Waterfall返回值向下传递
Bail返回非 undefined 截断
Loop返回非 undefined 重新开始
同步注册hook.tap(name, fn)
同步调用hook.call(...args)
异步回调注册hook.tapAsync(name, fn)
异步回调调用hook.callAsync(...args, cb)
Promise 注册hook.tapPromise(name, fn)
Promise 调用hook.promise(...args)

八、常见问题

问题答案
tapable 是什么?Webpack 插件系统的核心库,提供钩子机制
为什么 Webpack 需要 tapable?实现插件化架构,控制构建流程生命周期
Bail 什么时候用?需要提前终止的场景(如校验失败)
Waterfall 什么时候用?需要链式处理数据的场景
tap 和 register 区别?tap 只能观察,register 可以修改 tap 对象

九、延伸学习