webpack 模块图 第 二 节

ModuleGraph 是 Webpack 内部构建模块依赖图的核心管理器,负责追踪模块之间的引用、组织依赖关系、辅助生成优化后的构建结果。

🧱 核心数据结构

  • this._dependencyMap

    • 类型:WeakMap<Dependency, ModuleGraphConnection | null>
    • 用途:存储无来源模块的依赖与连接关系
  • this._moduleMap

    • 类型:Map<Module, ModuleGraphModule>
    • 用途:记录模块对应的图节点信息
  • this._metaMap

    • 类型:WeakMap<any, object>
    • 用途:用于记录任意对象的元信息(缓存用途)
  • this._cache

    • 类型:WeakTupleMap<any[], any> | undefined
    • 用途:内部使用的缓存机制
  • this._moduleMemCaches

    • 类型:Map<Module, WeakTupleMap<any, any>> | undefined
    • 用途:模块级别的缓存存储
  • this._cacheStage

    • 类型:string | undefined
    • 用途:用于标识当前缓存的构建阶段

⚙️ 主要方法功能分类

  • 依赖与模块关系管理

    • setResolvedModule:设置依赖指向的模块连接
    • updateModule:更新依赖对应的模块
    • removeConnection:移除依赖与模块的连接
    • addExplanation:为依赖连接添加说明信息
  • 模块图节点处理

    • _getModuleGraphModule:获取或新建模块图节点
    • cloneModuleAttributes:复制模块的遍历信息(用于图遍历)
    • removeModuleAttributes:清空单个模块的图信息
    • removeAllModuleAttributes:清空所有模块图信息(如重构阶段)
  • 依赖父级管理

    • setParents:设置依赖的父模块、父块和索引
    • getParentModule:获取依赖的父模块
    • getParentBlock:获取依赖的父块
    • getParentBlockIndex:获取依赖在块中的位置
  • 模块连接迁移

    • moveModuleConnections:将符合条件的连接从一个模块迁移到另一个模块(比如合并模块时使用)
js 复制代码
constructor() {
	// 用于存储依赖到连接(ModuleGraphConnection)的映射,使用 WeakMap 防止内存泄漏
	this._dependencyMap = new WeakMap();

	// 存储模块(Module)到 ModuleGraphModule 的映射
	this._moduleMap = new Map();

	// 存储任意对象到元数据对象的映射,用于缓存额外信息,使用 WeakMap 避免泄漏
	this._metaMap = new WeakMap();

	// 一个弱元组键缓存,用于性能优化(可选)
	this._cache = undefined;

	// 每个模块对应一个弱元组缓存,用于模块级的缓存加速(可选)
	this._moduleMemCaches = undefined;

	// 缓存阶段标志(例如构建过程中的某个阶段标识)
	this._cacheStage = undefined;
}

/**
 * 获取一个模块对应的 ModuleGraphModule 对象,若不存在则创建并保存
 * @param {Module} module 模块对象
 * @returns {ModuleGraphModule}
 */
_getModuleGraphModule(module) {
	let mgm = this._moduleMap.get(module);
	if (mgm === undefined) {
		mgm = new ModuleGraphModule();
		this._moduleMap.set(module, mgm);
	}
	return mgm;
}

/**
 * 设置某个依赖的"父模块"、"父依赖块"以及在块中的索引
 * @param {Dependency} dependency 当前依赖对象
 * @param {DependenciesBlock} block 父依赖块
 * @param {Module} module 所属的模块
 * @param {number=} indexInBlock 在依赖块中的位置(默认 -1)
 */
setParents(dependency, block, module, indexInBlock = -1) {
	dependency._parentDependenciesBlockIndex = indexInBlock;
	dependency._parentDependenciesBlock = block;
	dependency._parentModule = module;
}

/**
 * 获取某依赖所属的父模块
 * @param {Dependency} dependency 当前依赖
 * @returns {Module | undefined}
 */
getParentModule(dependency) {
	return dependency._parentModule;
}

/**
 * 获取某依赖所属的父依赖块
 * @param {Dependency} dependency 当前依赖
 * @returns {DependenciesBlock | undefined}
 */
getParentBlock(dependency) {
	return dependency._parentDependenciesBlock;
}

/**
 * 获取依赖在其父依赖块中的位置索引
 * @param {Dependency} dependency 当前依赖
 * @returns {number}
 */
getParentBlockIndex(dependency) {
	return dependency._parentDependenciesBlockIndex;
}

/**
 * 为某个依赖设置其所解析到的模块,并在模块图中建立连接关系
 * @param {Module | null} originModule 源模块(发起引用)
 * @param {Dependency} dependency 当前依赖
 * @param {Module} module 目标模块(被引用)
 */
setResolvedModule(originModule, dependency, module) {
	const connection = new ModuleGraphConnection(
		originModule,
		dependency,
		module,
		undefined,
		dependency.weak,
		dependency.getCondition(this)
	);

	// 将连接添加到目标模块的"传入连接"中
	const connections = this._getModuleGraphModule(module).incomingConnections;
	connections.add(connection);

	if (originModule) {
		const mgm = this._getModuleGraphModule(originModule);
		if (mgm._unassignedConnections === undefined) {
			mgm._unassignedConnections = [];
		}
		mgm._unassignedConnections.push(connection);

		// 若源模块还未有 outgoingConnections,初始化它
		if (mgm.outgoingConnections === undefined) {
			mgm.outgoingConnections = new SortableSet();
		}
		mgm.outgoingConnections.add(connection);
	} else {
		// 若没有 originModule,就直接记录在 dependencyMap 中
		this._dependencyMap.set(dependency, connection);
	}
}

/**
 * 更新依赖的目标模块,并将连接替换为新模块
 * @param {Dependency} dependency 当前依赖
 * @param {Module} module 新的目标模块
 */
updateModule(dependency, module) {
	const connection = /** @type {ModuleGraphConnection} */ (this.getConnection(dependency));
	if (connection.module === module) return;

	const newConnection = connection.clone();
	newConnection.module = module;
	this._dependencyMap.set(dependency, newConnection);
	connection.setActive(false);

	const originMgm = this._getModuleGraphModule(/** @type {Module} */ (connection.originModule));
	originMgm.outgoingConnections.add(newConnection);

	const targetMgm = this._getModuleGraphModule(module);
	targetMgm.incomingConnections.add(newConnection);
}

/**
 * 移除某个依赖的连接关系
 * @param {Dependency} dependency 当前依赖
 */
removeConnection(dependency) {
	const connection = /** @type {ModuleGraphConnection} */ (this.getConnection(dependency));
	const targetMgm = this._getModuleGraphModule(connection.module);
	targetMgm.incomingConnections.delete(connection);

	const originMgm = this._getModuleGraphModule(/** @type {Module} */ (connection.originModule));
	originMgm.outgoingConnections.delete(connection);

	this._dependencyMap.set(dependency, null);
}

/**
 * 给某个依赖的连接添加说明文字(一般用于调试或解释)
 * @param {Dependency} dependency 当前依赖
 * @param {string} explanation 描述信息
 */
addExplanation(dependency, explanation) {
	const connection = /** @type {ModuleGraphConnection} */ (this.getConnection(dependency));
	connection.addExplanation(explanation);
}

/**
 * 克隆源模块上的图属性(例如深度、索引等)到目标模块上
 * @param {Module} sourceModule 源模块
 * @param {Module} targetModule 目标模块
 */
cloneModuleAttributes(sourceModule, targetModule) {
	const oldMgm = this._getModuleGraphModule(sourceModule);
	const newMgm = this._getModuleGraphModule(targetModule);
	newMgm.postOrderIndex = oldMgm.postOrderIndex;
	newMgm.preOrderIndex = oldMgm.preOrderIndex;
	newMgm.depth = oldMgm.depth;
	newMgm.exports = oldMgm.exports;
	newMgm.async = oldMgm.async;
}

/**
 * 移除某个模块上的遍历属性和 async 标志
 * @param {Module} module 要清理的模块
 */
removeModuleAttributes(module) {
	const mgm = this._getModuleGraphModule(module);
	mgm.postOrderIndex = null;
	mgm.preOrderIndex = null;
	mgm.depth = null;
	mgm.async = false;
}

/**
 * 移除所有模块上的遍历属性(清空状态)
 */
removeAllModuleAttributes() {
	for (const mgm of this._moduleMap.values()) {
		mgm.postOrderIndex = null;
		mgm.preOrderIndex = null;
		mgm.depth = null;
		mgm.async = false;
	}
}

/**
 * 将某个模块上的连接迁移到另一个模块(满足过滤条件的连接)
 * @param {Module} oldModule 原始模块
 * @param {Module} newModule 新模块
 * @param {function(ModuleGraphConnection): boolean} filterConnection 判断哪些连接需要迁移
 */
moveModuleConnections(oldModule, newModule, filterConnection) {
	if (oldModule === newModule) return;

	const oldMgm = this._getModuleGraphModule(oldModule);
	const newMgm = this._getModuleGraphModule(newModule);

	// 处理 outgoing connections(从 oldModule 出发)
	const oldConnections = oldMgm.outgoingConnections;
	if (oldConnections !== undefined) {
		if (newMgm.outgoingConnections === undefined) {
			newMgm.outgoingConnections = new SortableSet();
		}
		const newConnections = newMgm.outgoingConnections;
		for (const connection of oldConnections) {
			if (filterConnection(connection)) {
				connection.originModule = newModule;
				newConnections.add(connection);
				oldConnections.delete(connection);
			}
		}
	}

	// 处理 incoming connections(指向 oldModule)
	const oldConnections2 = oldMgm.incomingConnections;
	const newConnections2 = newMgm.incomingConnections;
	for (const connection of oldConnections2) {
		if (filterConnection(connection)) {
			connection.module = newModule;
			newConnections2.add(connection);
			oldConnections2.delete(connection);
		}
	}
}
相关推荐
打野赵怀真8 分钟前
React Hooks 的优势和使用场景
前端·javascript
HaushoLin12 分钟前
ERR_PNPM_DLX_NO_BIN No binaries found in tailwindcss
前端·vue.js·css3·html5
Lafar12 分钟前
Widget 树和 Element 树和RenderObject树是一一 对应的吗
前端
小桥风满袖14 分钟前
炸裂,前端神级动效库合集
前端·css
匆叔15 分钟前
Tauri 桌面端开发
前端·vue.js
1_2_3_15 分钟前
react-antd-column-resize(让你的table列可以拖拽列宽)
前端
Lafar15 分钟前
Flutter和iOS混合开发
前端·面试
九龙湖兔兔16 分钟前
pnpm给插件(naiveUI)打补丁
前端·架构
知心宝贝17 分钟前
【Nest.js 通关秘籍 - 基础篇】带你轻松掌握后端开发
前端·javascript·架构