webpack源码分析——tapable中before和stage如何改变执行顺序

一、Before用法

Before 用法

before 属性的值可以传入一个数组或者字符串,值为注册事件对象时的名称,它可以修改当前事件函数在传入的事件名称对应的函数之前进行执行。

示例

js 复制代码
 let hook = new SyncWaterfallHook(['arg1']);

 hook.tap('tap1', (arg)=> {
	console.log('tap1', arg);
	return '返回值';
 });

 hook.tap({
	name: 'tap2',
	before: 'tap1' // 这里调换了顺序,把 tap2 执行顺序调整到 tap1 之前
 }, (arg)=> {
	console.log('tap2', arg);
	return '返回2';
 })

 hook.tap('tap3', (arg)=> {
	console.log('tap3', arg);
	return '返回3';
 })

 hook.call('yun');


原理描述

tap 函数把注册函数以一下方式放入taps队列

第一次调用tap函数进行注册函数时:

  1. 使用 _insert 函数完成注册函数进入队列
  2. 因为此时taps队列中还没有函数,所以直接当前注册函数通过 this.tapsi = item; 推入队列中

第二次调用tap函数进行注册函数时:

  1. 使用 _insert 函数完成注册函数进入队列
  2. 如果taps 中有函数,则进入 while 循环
  3. 把taps队列中最后一个函数(下标为0的函数)取出赋值给x变量中并为队列长度延长一个位置后(当前队列长度为2),把x变量赋值给刚才延长的位置上(刚才延长的位置下标为1)
  4. 把要注册的函数放入下标为1的位置

第三、四...次函数注册和第二次注册处理逻辑一致(只是下标有所不同)。

示例:

tap2是要插入的函数

循环前未插入tap2时:taps:'tap1'

循环前未准备插入tap2时:taps:'tap1','tap1'

不再循环完成插入时:taps:'tap1', 'tap2'
tap3是要插入的函数

循环前未插入tap3时:taps:'tap1', 'tap2'

循环前未准备插入tap3时:taps:'tap1','tap2','tap2'

不再循环完成插入时:taps:'tap1','tap2', 'tap3'

当使用 before 进行改变插入顺序时逻辑如下

第一次插入注册函数和上方一致

第二次调用tap函数进行注册函数时:

  1. 使用 _insert 函数完成注册函数进入队列
  2. taps 中已经有函数了,因此进入 while 循环
  3. 把taps队列中最后一个函数取出赋值给x变量中并为队列长度延长一个位置后,把x变量赋值给刚才延长的位置上
  4. 判断是否存在before,如果不存在就没有改变插入顺序
  5. 如果存在,判断当前x.name是不是要被插入的函数
  6. 如果是则continue
  7. 如果循环条件不满足则退出循环
  8. 最后插入被插入函数的前面

第三、四...次函数注册和第二次注册处理逻辑一致。

示例:

tap3是要插入tap1的函数前面

循环前未插入tap3时:taps:'tap1', 'tap2'

循环前未准备插入tap3时:taps:''tap1','tap2','tap2'

循环前未准备插入tap3时:taps:''tap1','tap1','tap2'

不再循环完成插入时:taps:'tap3','tap1', 'tap2'

二、stage 用法

stage 用法

stage 类型是数字,数字越大事件回调执行的越晚,支持传入负数,不传时默认为0.

示例

js 复制代码
let hook = new SyncWaterfallHook(['arg1']);

 hook.tap('tap1', (arg)=> {
	console.log('tap1', arg);
	return '返回值';
 });

 hook.tap({
	name: 'tap2',
	stage: 2
 }, (arg)=> {
	console.log('tap2', arg);
	return '返回2';
 })

 hook.tap('tap3', (arg)=> {
	console.log('tap3', arg);
	return '返回3';
 })

 hook.tap({
	name: 'tap4',
	stage: 1
 }, (arg)=> {
	console.log('tap4', arg);
	return '返回4';
 })

 hook.call('yun');


原理描述

原理基本和Before一致。

示例:

已知tap1,tap2 已在taps队列中 taps:'tap1-stage:0', 'tap2-stage:0'

先插入tap3-stage3和tap4-stage2

循环前未插入tap3时:taps:''tap1-stage:0', ''tap2-stage:0'

循环前未准备插入tap3时:taps:''tap1-stage:0',''tap2-stage:0',''tap2-stage:0'

不再循环完成插入时:taps:''tap1-stage:0',''tap2-stage:0',''tap3-stage:3'
循环前未插入tap4时:taps:''tap1-stage:0',''tap2-stage:0',''tap3-stage:3'

循环前未准备插入tap4时:taps:''tap1-stage:0',''tap2-stage:0',''tap3-stage:3', ''tap3-stage:3'

循环前未准备插入tap4时:taps:''tap1-stage:0',''tap2-stage:0',''tap3-stage:0', ''tap3-stage:3'

不再循环完成插入时:taps:''tap1-stage:0',''tap2-stage:0','"'tap4-stage:2",'tap3-stage:3'

三、源码

js 复制代码
_resetCompilation() {
	this.call = this._call;
	this.callAsync = this._callAsync;
	this.promise = this._promise;
}

_insert(item) {
		this._resetCompilation(); // 重置有关配置
		let before;
		if (typeof item.before === "string") { // 判断传入的 before 字段类型,下方统一为Set类型
			before = new Set([item.before]);
		} else if (Array.isArray(item.before)) {
			before = new Set(item.before);
		}
		let stage = 0;
		if (typeof item.stage === "number") {
			stage = item.stage;
		}
		let i = this.taps.length; // 获取注册的tap函数个数
		while (i > 0) {
			i--;
			const x = this.taps[i];
			this.taps[i + 1] = x; 
			const xStage = x.stage || 0;
			if (before) { // 判断是否可以插入
				if (before.has(x.name)) { // 当前x.name是否是被插入的函数
					before.delete(x.name);
					continue; // 继续循环
				}
				if (before.size > 0) {
					continue; // 继续循环
				}
			}
			if (xStage > stage) {
				continue;
			}
			i++; 
			break;
		}
		this.taps[i] = item;
	}
相关推荐
无限压榨切图仔4 分钟前
从 Claude Code 切到 Codex:我用 Agent、Skills、MCP 做完了一个内容运营工具
前端·后端
濮水大叔7 分钟前
NestJS 与 CabloyJS 的 env/config 架构对比:从环境变量到实例级配置
前端·node.js·nestjs
成都渲染101云渲染66668 分钟前
Blender渲染时,纯CPU渲染的设置教程
前端·javascript·blender
董员外11 分钟前
RAG 系统进化论(一):纵览 RAG 的发展历程
前端·人工智能·后端
多加点辣也没关系30 分钟前
JavaScript|第30章:事件机制
开发语言·javascript
Gauss松鼠会42 分钟前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
YHHLAI1 小时前
Vue 3 流式输出实战:从零掌握 LLM Streaming 与 SSE 协议
前端·javascript·vue.js
2601_965798471 小时前
Arrow Unlocker HTML5 Game Review: Keep Web Arcade Players Coming Back
前端·html·html5
不简说2 小时前
JS 代码技巧 vol.10 — 20 个 V8 引擎原理,解释"为什么这样写快"
前端·javascript·面试
醉城夜风~2 小时前
HTML常用标签详解学习博客:从基础标签到页面结构完整掌握
前端·学习·html