深入Webpack之Tapable详解

Tapable是什么

  • 官网的解释:是 webpack 的一个核心工具,但也可用于其他地方, 以提供类似的插件接口。 在 Webpack 中的许多对象都扩展自 Tapable 类。 它对外暴露了 taptapAsynctapPromise 等方法, 插件可以使用这些方法向 Webpack 中注入自定义构建的步骤,这些步骤将在构建过程中触发。
  • Webpack本质上是基于事件流的。它的工作流程就是将各个插件串联起来,而实现这一切的核心就是tapable,核心原理是依赖于发布订阅模式(统一由调度中心进行处理,订阅者和发布者互不干扰)。Webpack 的生命周期是通过 Tapable 提供的钩子类来实现的。
  • tapable 注册函数 的方法有三种:tap(同步)、tapAsync(异步)、tapPromise(异步)。
  • 相对应的 执行方法 有三种:call(同步)、callAsync(异步)、promise(异步)。

注册/执行方法

  • tap/call

用于同步、异步钩子

  • tapAsync/callAsync

用于异步钩子,用 tabAsync 注册的事件处理函数最后一个参数都为一个回调函数 callback,每个事件处理函数在异步代码执行完毕后调用 callback 函数,则可以保证 callAsync 会在所有异步函数都执行完毕后执行。

  • tapPromise/promise

用于异步钩子,用 tapPromise 注册事件处理函数,必须返回一个 Promise 实例,而 promise 方法也返回一个 Promise 实例,并将返回值传入 resolve 方法

Tapable钩子

按照命名可以将钩子分为这几类:

  • waterfallHook:将上一个监听的执行结果当作下一个监听的入参

  • bailHook:执行到返回结果为非undefined的监听时停止监听

  • seriesHook:串行执行异步监听

  • parallelHook:并行执行异步监听

  • SyncHook

同步串行钩子,不关心事件处理函数的返回值,在触发事件时之后,会按照事件注册的先后顺序执行所有的事件处理函数。

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

// 创建实例
let syncHook = new SyncHook(["name", "age"]);

// 注册事件
syncHook.tap("1", (name, age) => console.log("No.1", name, age));
syncHook.tap("2", (name, age) => console.log("No.2", name, age));
syncHook.tap("3", (name, age) => console.log("No.3", name, age));

// 触发事件,让监听函数执行
syncHook.call("cookie", 8); // cookie是我的小猫,嘿嘿,8个月大啦

// 输出结果
// No.1 cookie 8
// No.2 cookie 8
// No.3 cookie 8

简单实现原理:

class SyncHook {
	constructor() {
	  this.hooks = [];
	}
	// 定义注册事件
	tap(name, fn) {
	  this.hooks.push(fn);
	}
	// 定义触发事件(按注册事件的先后顺序执行)
	call(...args) {
	  this.hooks.forEach((hook) => hook(...args));
	}
}
  • SyncBailHook

同步串行熔断保险钩子,与 SyncHook 相似,但当返回的内容为非undefined时,会停止执行监听函数

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

// 创建实例
let syncBailHook = new SyncBailHook(["name", "age"]);

// 注册事件
syncBailHook.tap("1", (name, age) => console.log("No.1", name, age));
syncBailHook.tap("2", (name, age) => {
	console.log("No.2", name, age);
	return null
});
syncBailHook.tap("3", (name, age) => console.log("No.3", name, age));

// 触发事件,让监听函数执行
syncBailHook.call("cookie", 8); 

// 输出结果
// No.1 cookie 8
// No.2 cookie 8

简单实现原理:

class SyncBailHook {
	constructor(args){
		this.hooks = [];
	}
	// 定义注册事件
	tap(name, fn){
		this.hooks.push(fn);
	}
	// 定义触发事件
	call(...args){
		let result = undefined; // 当前函数的返回值
		let index = 0;// 当前是第几个函数
		do{
			result = this.hooks[index](...args);
			index++;
		}while(result === undefined && this.hooks.length > index);
	}
}
  • SyncWaterfallHook

同步串行瀑布钩子,前一个的返回值作为后一个的入参,需要考虑执行的顺序,需要且只能传一个形参

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

// 创建实例
let syncWaterfallHook = new SyncWaterfallHook(["name"]);

// 注册事件
syncWaterfallHook.tap("1", (name) => {
	console.log("No.1", name);
	return "north"; // north也是我的小猫
});
syncWaterfallHook.tap("2", (name) => {
	console.log("No.2", name);
	return "south"
});
syncWaterfallHook.tap("3", (name) => {
	console.log("No.3", name)
});

// 触发事件,让监听函数执行
syncWaterfallHook.call("cookie"); 

// 输出结果
// No.1 cookie
// No.2 north
// No.3 south

简单实现原理:

class SyncBailHook {
	constructor(args){
		this.hooks = [];
	}
	// 定义注册事件
	tap(name, fn){
		this.hooks.push(fn);
	}
	// 定义触发事件
	call(){
		let result; // 当前函数的返回值
    	this.hooks.forEach((hook, index) => {
    		// 当执行第一个函数时,将call传入的参数作为下一个函数的入参,之后的函数将上一个函数的返回值作为下一个函数的入参
      		result = index === 0 ? hook(...arguments) : hook(result);
    	});
	}
}
  • SyncLoopHook

同步串行循环钩子,当遇到某个函数返回非undefined的函数时重复循环

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

// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);

// 定义辅助变量
let total = 0;

// 注册事件
syncLoopHook.tap("1", (name, age) => {
	console.log("No.1", name, age, total);
	return total++ <= 2 ? "继续循环" : undefined
	});
syncLoopHook.tap("2", (name, age) => console.log("No.2", name, age, total));

// 触发事件,让监听函数执行
syncLoopHook.call("cookie", 8);

// 输出结果
// No.1 cookie 8 0
// No.1 cookie 8 1
// No.1 cookie 8 2
// No.2 cookie 8 3

简单实现原理:参考https://juejin.cn/post/6844903812705026055

注意:同步的钩子必须用tap注册

  • AsyncParallelHook

异步并联钩子,callback 方法是为了检测是否已经满足条件执行 callAsync 的回调,如果中间某个事件处理函数没有调用 callback,只是不会调用 callAsync 的回调,但是所有的事件处理函数都执行了。

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

// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 注册事件
asyncParallelHook.tapAsync("1", (name, age, callback) => {
	setTimeout(() => {
		console.log("No.1", name, age);
		callback();
	}, 1000)
});
asyncParallelHook.tapAsync("2", (name, age, callback) => {
	setTimeout(() => {
		console.log("No.2", name, age);
		callback();
	}, 2000)
});

// 触发事件,让监听函数执行
asyncParallelHook.callAsync("cookie", 8, () => console.log("异步结束")); 

// 输出结果
// No.1 cookie 8
// No.2 cookie 8
// 异步结束

异步并行,上例中的两个定时器最长时长为2s,且两个函数是并行的,这个例子一共执行了2s左右

  • AsyncParallelBailHook

异步并联保险钩子,当返回的内容为非undefined时,会停止执行监听函数

  • AsyncSeriesHook

异步串联钩子,在注册事件的回调中如果不调用 callback,则在触发事件时会在没有调用 callback 的事件处理函数的位置 “卡死”,即不会继续执行后面的事件处理函数,只有调用 callback 才能继续,而最后一个事件处理函数中调用 callback 决定是否调用 callAsync 的回调。
异步串联,结合AsyncParallelHook的例子,两个函数是串行的,所以一共执行了1s+2s=3s

  • AsyncSeriesBailHook

异步串联保险钩子,当返回的内容为非undefined时,会停止执行监听函数

  • AsyncSeriesWaterfallHook

异步串联瀑布钩子,前一个的返回值作为后一个的入参,需要且只能传一个形参

未完待续~~
剩余部分:tapable的应用场景等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值