二、tapable 库介绍
2.1 基本信息
| 项目 | 内容 |
|---|---|
| 组织 | webpack 组织 |
| 用途 | 为插件系统提供钩子机制 |
| GitHub | https://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: hello3.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: 13.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 区别
| 属性 | tap | register |
|---|---|---|
| 触发时机 | 注册时 | 注册时 |
| 入参 | tap 对象 | tap 对象 |
| 返回值 | void | tap 对象 |
| 能否修改 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 对象 |
九、延伸学习
- tapable GitHub 仓库
- Webpack 插件开发指南
- 下一节:tapable 实践练习