跳到主内容

Webpack核心库Tapable的使用教程

· 5分钟阅读

Webpack 作为一款优秀的构建工具,使用了 Tapable 库来处理整个构建生命周期的发布和订阅,那么 Webpack 的 Tapable 库实现的原理是怎样的,本文将分析下 Tapable 这个库的使用方法。

hook 类型

Tapable 提供了多种 hook 的类型,有 Basic hook,Waterfall,Bail,Loop 四大种

Basic hook : 基础类型,实现多个订阅的顺序执行,多个订阅函数无通信。

Waterfall hook : 瀑布流,实现多个订阅的顺序执行,但是返回值给下一个订阅函数使用。

Bail hook : 允许提前终止执行,如果有一个订阅函数返回了非 undefined 值,则终止所有订阅函数的执行。

Loop hook : 循环执行,多个钩子轮流执行,直到某一个订阅函数返回值是 undefined

Webpack 的 Compiler 和 Compilation 都是继承自 Tapable 类的,通过源码可以看出

class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
// ...
make: new AsyncParallelHook(["compilation"]),
emit: new AsyncSeriesHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
// ...
};
}
}

这里的 AsyncSeriesHookAsyncParallelHook 都是 Tapable 这个库提供的创建 hook 实例的方法,除此之外还有其他 hook

const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook,
} = require("tapable");

从名称可以看出,Tapable 提供的 hook 主要分为同步和异步两大种类,下面分别提取一种进行介绍

SyncHook

SyncHook 用来创建同步 hook,通过 tap 来监听,通过 call 来触发

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

const hook = new SyncHook(["name"]);

hook.tap("start", (args) => {
console.log("start", args);
});

hook.tap("end", (args) => {
console.log("end", args);
});

hook.tap(
{
name: "build",
before: "end", // 在 end 之前执行
},
(args) => {
console.log("build", args);
}
);

hook.call("hello");

// start hello
// build hello
// end hello

SyncBailHook

SyncBailHook 用来创建同步保险 hook,当返回一个非 undefined 的值,则后续监听的事件的就不会再触发,下面的结果只是触发了 start 事件 hook

hook.tap("start", (args) => {
console.log("start", args);
return true;
});

hook.tap("end", (args) => {
console.log("end", args);
});

hook.call("hello");
// start hello

SyncWaterfallHook

SyncWaterfallHook 也是创建同步 hook ,但是事件 return 出来的结果都会给下一次事件回调接收

const hook = new SyncWaterfallHook(["name"]);

hook.tap("start", (args) => {
console.log("start", args);
return "start返回参数";
});

hook.tap("end", (args) => {
console.log("end", args);
});

hook.call("hello");

SyncLoopHook

SyncLoopHook 也是创建同步 hook,但是如果事件回调会无限出发,直到回调返回 undefined 为止

const hook = new SyncLoopHook(["name"]);
let index = 1;

hook.tap("loop", (args) => {
return index++ === 3 ? undefined : true;
});

hook.call();

AsyncParallelHook

AsyncParallelHook 用来创建异步 hook,需要通过 callAsync 来触发,按顺序执行

const hook = new AsyncParallelHook(["name"]);

hook.tapAsync("start", (name, cb) => {
setTimeout(() => {
console.log("开始");
cb(); // 如果cb有值,则直接结束,不过下面 end 事件回调还会触发
}, 2000);
});

hook.tapAsync("end", (name, cb) => {
setTimeout(() => {
console.log("结束");
cb("end finish");
}, 3000);
});

hook.callAsync("options", (args) => {
console.log("finish", args);
});

如果有回调里面抛出异常,则会立刻终止进入 callAsync 的回调

const hook = new AsyncParallelHook(["name"]);

hook.tapAsync("start", (name, cb) => {
setTimeout(() => {
console.log("开始");
throw new Error("出错了");
}, 2000);
});

hook.tapAsync("end", (name, cb) => {
setTimeout(() => {
console.log("结束");
cb("end finish");
}, 3000);
});

hook.callAsync("options", (args) => {
console.log("finish", args);
});

// 开始
// Error: 出错了

AsyncParallelBailHook

AsyncParallelBailHook 是用来创建异步 hook,功能与 AsyncParallelHook 类似,但是不同的是如果在第一个事件触发调用 callback 有值,可以传递给下一个事件,具体查看例子

const hook = new AsyncParallelBailHook(["name"]);

hook.tapAsync("start", (name, cb) => {
setTimeout(() => {
console.log("开始");
cb("start返回参数"); // 这里有参数传入,直接给 end 事件,而 AsyncParallelHook 则直接结束
}, 2000);
});

hook.tapAsync("end", (name, cb) => {
setTimeout(() => {
console.log("结束", name);
cb("end finish");
}, 3000);
});

hook.callAsync("options", (args) => {
console.log("finish", args);
});

// 开始
// finish start返回参数
// 结束 options