tapable 是 Webpack 插件系统的核心依赖库,提供各类 Hook(钩子)来实现构建流程生命周期的发布订阅。本文按"类型分类 → 执行机制 → 注册与调用 → 拦截器"的顺序梳理其核心知识。
| 项目 | 内容 |
|---|---|
| 组织 | webpack 组织 |
| 用途 | 为插件系统提供钩子机制 |
| GitHub | https://github.com/webpack/tapable |
tapable 导出的 Hook 类型:
├── 同步 Hooks(Sync)
│ ├── SyncHook - 基础同步钩子
│ ├── SyncBailHook - 熔断钩子(可截断)
│ ├── SyncWaterfallHook - 瀑布流钩子
│ └── SyncLoopHook - 循环钩子
│
└── 异步 Hooks(Async)
├── AsyncSeriesHook - 异步串行钩子
├── AsyncParallelHook - 异步并行钩子
└── ... 更多异步类型特点: 简单执行,每个 tap 的函数都会执行。
执行流程:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
执行 执行 执行
特点:依次执行,互不影响代码示例:
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特点: 上一个函数的返回值传递给下一个函数。
执行流程:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
return 1 接收 1 接收 2
return 2 return 3
特点:返回值像瀑布一样向下传递代码示例:
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特点: 任一函数返回非 undefined 值,立即停止后续执行。
执行流程:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘ └─────────┘ └─────────┘
│ │
▼ ▼
return return 'stop'
undefined (非 undefined)
│ │
▼ ▼
继续执行 停止!C 不执行
特点:可截断,类似电路中的保险丝代码示例:
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 不会执行特点: 返回非 undefined 值时,从第一个重新开始。
执行流程:
第一轮:A → B → C(C 返回非 undefined)
│
▼ 重新开始
第二轮:A → B → C(C 返回 undefined)
│
▼ 结束
流程完成
特点:循环执行直到所有函数都返回 undefined代码示例:
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 次)┌─────────────────────────────────────────────┐
│ 同步 Hook │
├─────────────────────────────────────────────┤
│ │
│ 注册:hook.tap(name, callback) │
│ │
│ 调用:hook.call(...args) │
│ │
│ 特点: │
│ - 依次调用,等待上一个完成 │
│ - 同步阻塞执行 │
│ │
└─────────────────────────────────────────────┘异步分为串行(Series)和并行(Parallel):
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Plugin A │ ──► │ Plugin B │ ──► │ Plugin C │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
callback callback callback
完成 → 完成 → 完成
特点:依次执行,等待上一个完成后才执行下一个 ┌─────────┐
│ Plugin A │ ────►
└─────────┘
│
┌────────┴────────┐
│ │
┌─────────┐ ┌─────────┐
│ Plugin B │ ────► │ Plugin C │ ────►
└─────────┘ └─────────┘
│ │
└────────┬────────┘
▼
全部完成 → callback
特点:同时执行,等待全部完成后回调| 注册方法 | 调用方法 | 说明 |
|---|---|---|
hook.tap(name, callback) | hook.call(...args) | 同步注册与调用 |
hook.tapAsync(name, callback) | hook.callAsync(...args, callback) | 异步回调式注册与调用 |
hook.tapPromise(name, callback) | hook.promise(...args) | Promise 式注册与调用 |
const { SyncHook } = require('tapable');
const hook = new SyncHook(['arg1', 'arg2']);
// 注册
hook.tap('MyPlugin', (arg1, arg2) => {
console.log(arg1, arg2);
});
// 调用
hook.call('hello', 'world');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('全部完成');
});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('全部完成');
});作用: 在 Hook 执行过程中插入观察/处理逻辑。
hook.intercept({
call: (...args) => {
// 调用时触发
console.log('调用参数:', args);
},
tap: (tap) => {
// 每次 Hook 被调用时、在每个 tap 执行前触发(观察)
console.log('即将执行插件:', tap.name);
},
loop: (...args) => {
// Loop 类型每轮开始时触发
console.log('新一轮循环');
},
register: (tap) => {
// 注册时触发(可修改 tap 对象)
tap.name = 'Modified:' + tap.name;
return tap; // 返回修改后的对象
}
});| 属性 | tap | register |
|---|---|---|
| 触发时机 | 调用时(每个 tap 执行前) | 注册时 |
| 入参 | tap 对象 | tap 对象 |
| 返回值 | void | tap 对象 |
| 能否修改 tap | 不能 | 可以 |
| 典型用途 | 观察流程、日志 | 修改 tap 对象 |
使用建议:
如果不需要修改 tap 对象,优先使用
tap属性进行观察。
┌─────────────────────────────────────────────────────────────┐
│ tapable 核心知识 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Hook 类型分类 │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ Basic │ 依次执行,互不影响 │ │
│ │ Waterfall │ 返回值传递给下一个函数 │ │
│ │ Bail │ 非 undefined 返回值截断后续执行 │ │
│ │ Loop │ 非 undefined 返回值触发重新开始 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 执行机制 │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ 同步 │ tap + call │ │
│ │ 异步串行 │ tapAsync + callAsync / tapPromise + promise │
│ │ 异步并行 │ 多个同时执行,等待全部完成 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 拦截器(Intercept) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ call │ 调用时触发 │ │
│ │ tap │ 注册时观察 │ │
│ │ loop │ 循环每轮开始时触发 │ │
│ │ register │ 注册时可修改 tap 对象 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘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 → 结束
↑___________________________________|| 类别 | 要点 |
|---|---|
| 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 在每次调用时(tap 执行前)观察,register 在注册时触发且可修改 tap 对象 |