webpack 检出图 第 四 节 ChunkGraph.js

138 阅读5分钟

🔧 模块资源类型相关(SourceTypes)

  • setChunkModuleSourceTypes(chunk, module, sourceTypes)
    为某个 chunk 中的某个模块设置 sourceTypes,并更新缓存。
  • getChunkModuleSourceTypes(chunk, module)
    获取某个 chunk 中某个模块的 sourceTypes,优先从缓存中取,默认使用模块自身的值。
  • getModuleSourceTypes(module)
    获取某个模块在其所有 chunk 中合并后的 sourceTypes,用于跨 chunk 判断。
  • _getOverwrittenModuleSourceTypes(module)
    内部方法:合并所有包含该模块的 chunk 中的 sourceTypes,如果类型不同就合并成一个新集合。

📦 Chunk 模块集合操作

  • getOrderedChunkModulesIterable(chunk, comparator)
    获取某个 chunk 的模块集合(可迭代),并按给定排序函数排序。
  • getOrderedChunkModulesIterableBySourceType(chunk, sourceType, comparator)
    获取指定 chunk 中指定 sourceType 类型的模块集合(可迭代、已排序)。
  • getChunkModules(chunk)
    获取 chunk 中所有模块的数组(无序、缓存版本,不可修改)。
  • getOrderedChunkModules(chunk, comparator)
    获取 chunk 中所有模块的数组(有序、缓存版本,不可修改)。

🔗 Chunk 模块 ID/Hash 映射

  • getChunkModuleIdMap(chunk, filterFn, includeAllChunks = false)
    遍历 chunk 的所有异步子 chunk(或所有引用 chunk),获取符合条件的模块 ID 映射表。
    格式:{ chunkId: [moduleId, ...] }
  • getChunkModuleRenderedHashMap(chunk, filterFn, hashLength = 0, includeAllChunks = false)
    获取 chunk 的模块 ID 到模块渲染 hash 的映射表,可截断 hash 长度。
    格式:{ chunkId: { moduleId: hash } }
/**
 * 设置指定 chunk 中指定模块的 sourceTypes(资源类型集合)
 * @param {Chunk} chunk chunk 实例
 * @param {Module} module 模块实例
 * @param {Set<string>} sourceTypes 模块的资源类型集合
 */
setChunkModuleSourceTypes(chunk, module, sourceTypes) {
	// 获取与 chunk 相关联的内部结构
	const cgc = this._getChunkGraphChunk(chunk);
	// 如果还未初始化 sourceTypesByModule,就创建一个 WeakMap
	if (cgc.sourceTypesByModule === undefined) {
		cgc.sourceTypesByModule = new WeakMap();
	}
	// 设置模块对应的资源类型集合
	cgc.sourceTypesByModule.set(module, sourceTypes);
	// 通过 modulesBySourceType 工具函数重新计算反向映射缓存
	cgc._modulesBySourceType = modulesBySourceType(cgc.sourceTypesByModule);
}

/**
 * 获取某个 chunk 中某个模块的 sourceTypes
 * @param {Chunk} chunk chunk 实例
 * @param {Module} module 模块实例
 * @returns {SourceTypes} 模块对应的资源类型集合
 */
getChunkModuleSourceTypes(chunk, module) {
	const cgc = this._getChunkGraphChunk(chunk);
	// 如果没有缓存映射,则返回模块默认的资源类型
	if (cgc.sourceTypesByModule === undefined) {
		return module.getSourceTypes();
	}
	// 否则优先使用缓存的 sourceTypes,如果不存在则 fallback
	return cgc.sourceTypesByModule.get(module) || module.getSourceTypes();
}

/**
 * 获取某个模块所有 chunk 中合并后的 sourceTypes
 * @param {Module} module 模块实例
 * @returns {SourceTypes} 合并后的资源类型集合
 */
getModuleSourceTypes(module) {
	// 优先返回合并覆盖值,否则返回模块默认 sourceTypes
	return (
		this._getOverwrittenModuleSourceTypes(module) || module.getSourceTypes()
	);
}

/**
 * 遍历所有包含该模块的 chunk,合并其 sourceTypes
 * @param {Module} module 模块实例
 * @returns {Set<string> | undefined} 合并后的资源类型集合
 */
_getOverwrittenModuleSourceTypes(module) {
	let newSet = false; // 是否需要创建新的 Set 以合并 sourceTypes
	let sourceTypes; // 合并后的结果

	// 遍历包含该模块的所有 chunk
	for (const chunk of this.getModuleChunksIterable(module)) {
		const cgc = this._getChunkGraphChunk(chunk);
		// 如果没有定义 sourceTypesByModule,则说明未设置,直接返回
		if (cgc.sourceTypesByModule === undefined) return;
		const st = cgc.sourceTypesByModule.get(module); // 获取该 chunk 上该模块的 sourceTypes
		if (st === undefined) return;

		if (!sourceTypes) {
			// 第一次赋值,直接使用引用
			sourceTypes = st;
			continue;
		} else if (!newSet) {
			// 如果之前未创建新 Set,检查是否需要扩展集合
			for (const type of st) {
				if (!sourceTypes.has(type)) {
					// 一旦发现有新增的类型,就新建 Set 并标记
					newSet = true;
					sourceTypes = new Set(sourceTypes);
					sourceTypes.add(type);
				}
			}
		} else {
			// 如果已经是新 Set,则直接添加所有类型
			for (const type of st) sourceTypes.add(type);
		}
	}

	return sourceTypes; // 返回合并后的资源类型
}

/**
 * 获取某个 chunk 中模块列表的可迭代对象,并根据传入的 comparator 排序
 * @param {Chunk} chunk chunk 实例
 * @param {function(Module, Module): -1|0|1} comparator 排序函数
 * @returns {Iterable<Module>} 排序后的模块集合
 */
getOrderedChunkModulesIterable(chunk, comparator) {
	const cgc = this._getChunkGraphChunk(chunk);
	// 对模块集合进行排序
	cgc.modules.sortWith(comparator);
	// 返回排序后的模块列表
	return cgc.modules;
}

/**
 * 获取指定 sourceType 的模块列表,并排序
 * @param {Chunk} chunk chunk 实例
 * @param {string} sourceType 要筛选的资源类型
 * @param {function(Module, Module): -1|0|1} comparator 排序函数
 * @returns {Iterable<Module> | undefined} 对应资源类型的排序模块列表
 */
getOrderedChunkModulesIterableBySourceType(chunk, sourceType, comparator) {
	const cgc = this._getChunkGraphChunk(chunk);
	// 通过缓存中的 sourceType → modules 获取指定类型模块集合
	const modulesWithSourceType = cgc.modules
		.getFromUnorderedCache(cgc._modulesBySourceType)
		.get(sourceType);
	if (modulesWithSourceType === undefined) return;
	// 对该模块集合排序
	modulesWithSourceType.sortWith(comparator);
	return modulesWithSourceType;
}

/**
 * 获取某个 chunk 中所有模块(缓存版本,不排序,不能修改)
 * @param {Chunk} chunk chunk 实例
 * @returns {Module[]} 模块列表数组
 */
getChunkModules(chunk) {
	const cgc = this._getChunkGraphChunk(chunk);
	// 获取缓存数组(无序)
	return cgc.modules.getFromUnorderedCache(getArray);
}

/**
 * 获取某个 chunk 中排序后的模块列表(缓存版本,不可修改)
 * @param {Chunk} chunk chunk 实例
 * @param {function(Module, Module): -1|0|1} comparator 排序函数
 * @returns {Module[]} 排序后的模块数组
 */
getOrderedChunkModules(chunk, comparator) {
	const cgc = this._getChunkGraphChunk(chunk);
	const arrayFunction = createOrderedArrayFunction(comparator);
	// 获取排序后的模块数组(缓存中)
	return cgc.modules.getFromUnorderedCache(arrayFunction);
}

/**
 * 生成 chunk → 模块 ID 数组 的映射表
 * @param {Chunk} chunk 源 chunk
 * @param {ModuleFilterPredicate} filterFn 模块过滤函数
 * @param {boolean} includeAllChunks 是否包含所有引用的 chunk(true)或仅 async chunks(false)
 * @returns {Record<string|number, (string|number)[]>} 映射表
 */
getChunkModuleIdMap(chunk, filterFn, includeAllChunks = false) {
	const chunkModuleIdMap = Object.create(null);

	// 遍历所有异步 chunk 或所有引用的 chunk
	for (const asyncChunk of includeAllChunks
		? chunk.getAllReferencedChunks()
		: chunk.getAllAsyncChunks()) {
		let array;

		// 遍历该 chunk 中的模块(按 ID 排序)
		for (const module of this.getOrderedChunkModulesIterable(
			asyncChunk,
			compareModulesById(this)
		)) {
			// 模块满足过滤条件
			if (filterFn(module)) {
				if (array === undefined) {
					array = [];
					// 初始化该 chunk 对应的模块 ID 列表
					chunkModuleIdMap[asyncChunk.id] = array;
				}
				// 获取模块 ID 并加入数组
				const moduleId = this.getModuleId(module);
				array.push(moduleId);
			}
		}
	}

	return chunkModuleIdMap;
}

/**
 * 获取 chunk → moduleId → 渲染 hash 的映射表
 * @param {Chunk} chunk 源 chunk
 * @param {ModuleFilterPredicate} filterFn 模块过滤函数
 * @param {number} hashLength hash 截断长度(0 表示不截断)
 * @param {boolean} includeAllChunks 是否包含所有引用的 chunk
 * @returns {Record<string|number, Record<string|number, string>>} 最终映射表
 */
getChunkModuleRenderedHashMap(
	chunk,
	filterFn,
	hashLength = 0,
	includeAllChunks = false
) {
	const chunkModuleHashMap = Object.create(null);

	for (const asyncChunk of includeAllChunks
		? chunk.getAllReferencedChunks()
		: chunk.getAllAsyncChunks()) {
		let idToHashMap;

		for (const module of this.getOrderedChunkModulesIterable(
			asyncChunk,
			compareModulesById(this)
		)) {
			if (filterFn(module)) {
				if (idToHashMap === undefined) {
					idToHashMap = Object.create(null);
					chunkModuleHashMap[asyncChunk.id] = idToHashMap;
				}
				const moduleId = this.getModuleId(module);
				const hash = this.getRenderedModuleHash(module, asyncChunk.runtime);
				// 可能对 hash 进行截断(用于缩短输出)
				idToHashMap[moduleId] = hashLength
					? hash.slice(0, hashLength)
					: hash;
			}
		}
	}

	return chunkModuleHashMap;
}