5-1 webpack核心库tapable工作原理
tapable 是什么
tapable 是 Webpack 的核心事件系统库。Webpack 的整个构建流程就是由 tapable 管理的一系列钩子(Hook)驱动的——Compiler 和 Compilation 对象上定义了数十个钩子,Plugin 通过 tap(注册)这些钩子来介入构建流程的各个阶段。
简单来说,tapable 提供了一套发布-订阅模式的事件系统,但比普通的 EventEmitter 更加强大,支持同步/异步、串行/并行、瀑布流等多种执行方式。
tapable 的 Hook 类型
tapable 提供了 9 种类型的 Hook:
| Hook 类型 | 执行方式 | 返回值 | 使用场景 |
|---|---|---|---|
SyncHook | 同步串行 | 无 | 简单的生命周期通知 |
SyncBailHook | 同步串行 | 遇到非 undefined 返回值停止 | 条件中断 |
SyncWaterfallHook | 同步串行 | 上一个返回值传给下一个 | 数据管道 |
SyncLoopHook | 同步循环 | 返回非 undefined 则重新执行 | 循环直到满足条件 |
AsyncParallelHook | 异步并行 | 无 | 多个异步任务同时执行 |
AsyncParallelBailHook | 异步并行 | 遇到返回值立即结束 | 异步条件中断 |
AsyncSeriesHook | 异步串行 | 无 | 异步任务依次执行 |
AsyncSeriesBailHook | 异步串行 | 遇到返回值停止 | 异步条件中断 |
AsyncSeriesWaterfallHook | 异步串行 | 上一个结果传给下一个 | 异步数据管道 |
Hook 的三种注册方式
const { SyncHook } = require('tapable');
const hook = new SyncHook(['arg1', 'arg2']);
// 1. tap —— 基础注册
hook.tap('PluginA', (arg1, arg2) => {
console.log(arg1, arg2);
});
// 2. tapAsync —— 异步注册(回调风格)
hook.tapAsync('PluginB', (arg1, arg2, callback) => {
setTimeout(() => {
console.log(arg1, arg2);
callback();
}, 1000);
});
// 3. tapPromise —— 异步注册(Promise 风格)
hook.tapPromise('PluginC', (arg1, arg2) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(arg1, arg2);
resolve();
}, 1000);
});
});
javascript
Hook 的触发方式
// 同步触发
hook.call('value1', 'value2');
// 异步触发(回调)
hook.callAsync('value1', 'value2', (err) => {
console.log('所有回调执行完毕');
});
// 异步触发(Promise)
hook.promise('value1', 'value2').then(() => {
console.log('所有 Promise 执行完毕');
});
javascript
在 Webpack 中的应用
Webpack 的 Compiler 对象上定义了大量基于 tapable 的钩子:
// lib/Compiler.js(简化)
const { SyncHook, AsyncSeriesHook } = require('tapable');
class Compiler {
constructor() {
this.hooks = {
environment: new SyncHook([]),
afterEnvironment: new SyncHook([]),
beforeRun: new AsyncSeriesHook(['compiler']),
run: new AsyncSeriesHook(['compiler']),
compile: new SyncHook(['params']),
make: new AsyncSeriesHook(['compilation']),
emit: new AsyncSeriesHook(['compilation']),
done: new AsyncSeriesHook(['stats']),
};
}
}
javascript
一个典型的 Plugin 通过 tap 注册到这些钩子上:
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 在 emit 阶段执行自定义逻辑
console.log('Assets about to be emitted');
callback();
});
}
}
javascript
Hook 的拦截器(Interceptor)
tapable 还支持拦截器,可以在注册、调用、执行前后插入逻辑:
hook.intercept({
register: (tapInfo) => {
console.log(`${tapInfo.name} is registering`);
return tapInfo;
},
call: (arg1, arg2) => {
console.log('Hook is being called with:', arg1, arg2);
},
tap: (tapInfo) => {
console.log(`${tapInfo.name} is being tapped`);
},
loop: (...args) => {
console.log('Loop hook iteration');
}
});
javascript
参考资源
- tapable GitHub - 源码仓库
- Webpack Compiler Hooks - 官方钩子文档
↑