webpack 检出图 第 三 节 ChunkGraph.js

92 阅读5分钟

这段代码是 Webpack 构建阶段“模块与输出代码结构之间关系图”的核心实现之一,提供了模块替换、关系查询、chunk 构成等核心功能支持。

🌐 核心概念

  • Module: 表示一个模块(通常是一个 JS 文件)。
  • Chunk: 表示一组打包后的模块代码块(可能异步加载)。
  • ChunkGraphModule (cgm): 模块在图中的元信息(如所属 chunk、是否是入口模块等)。
  • ChunkGraphChunk (cgc): chunk 在图中的元信息(如包含的模块、运行时模块等)。
  • RuntimeModule: 专用于 runtime 的模块(如 webpack_require 的实现)。
  • Entrypoint: webpack 的入口定义,表示某个模块是 chunk 的起始点。

🧠 核心方法功能总结

✅ 1. 模块替换逻辑

  • replaceModule(oldModule, newModule)

    • oldModule 在所有 chunk 中替换为 newModule,确保 graph 中所有引用和结构正确更新。

    • 涉及:

      • 普通模块 (modules)
      • 入口模块 (entryModules)
      • 运行时模块 (runtimeModules, fullHashModules, dependentHashModules)

🔍 2. 查询模块是否在某处

  • isModuleInChunk(module, chunk):模块是否在指定 chunk 中
  • isModuleInChunkGroup(module, chunkGroup):模块是否在某个 chunkGroup(多个 chunk)中
  • isEntryModule(module):模块是否是某个 chunk 的入口模块

🔄 3. 获取模块关联的 chunks

  • getModuleChunksIterable(module):获取某模块对应的 chunk 集合(可迭代)
  • getOrderedModuleChunksIterable(module, sortFn):按指定排序方式返回 chunk 集合
  • getModuleChunks(module):获取模块的 chunk(缓存版本)
  • getNumberOfModuleChunks(module):获取模块关联 chunk 的数量

⚙️ 4. 获取模块的运行时环境

  • getModuleRuntimes(module):返回模块对应的运行时环境集合

📦 5. 获取 chunk 内模块的信息

  • getNumberOfChunkModules(chunk):chunk 中模块的总数
  • getNumberOfChunkFullHashModules(chunk):chunk 中用于 full hash 的模块数量
  • getChunkModulesIterable(chunk):chunk 中的模块集合
  • getChunkModulesIterableBySourceType(chunk, sourceType):按 sourceType 获取 chunk 中的模块集合

🧩 设计亮点

  • 使用了 _getChunkGraphModule_getChunkGraphChunk 来统一获取抽象结构,封装性强。
  • 模块和 chunk 的关系被抽象成图状结构,极易扩展、分析依赖。
  • 全面缓存(getFromCache, getFromUnorderedCache)优化性能,适合大项目构建场景。
  • 支持运行时代码模块(RuntimeModule)的替换与追踪,对 HMR 和 runtime 拆分支持好。
/**
 * @param {Module} oldModule the replaced module
 * @param {Module} newModule the replacing module
 * @returns {void}
 */
replaceModule(oldModule, newModule) { // 将旧模块 oldModule 替换为新模块 newModule
	const oldCgm = this._getChunkGraphModule(oldModule); // 获取旧模块对应的 ChunkGraphModule 对象
	const newCgm = this._getChunkGraphModule(newModule); // 获取新模块对应的 ChunkGraphModule 对象

	for (const chunk of oldCgm.chunks) { // 遍历旧模块所在的所有 chunk
		const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 对应的 ChunkGraphChunk 对象
		cgc.modules.delete(oldModule); // 从 chunk 的模块集合中移除旧模块
		cgc.modules.add(newModule); // 将新模块添加到该 chunk 的模块集合中
		newCgm.chunks.add(chunk); // 将 chunk 添加到新模块的 chunk 集合中
	}
	oldCgm.chunks.clear(); // 清空旧模块的 chunk 集合

	if (oldCgm.entryInChunks !== undefined) { // 如果旧模块在某些 chunk 中是入口模块
		if (newCgm.entryInChunks === undefined) { // 如果新模块还没有 entryInChunks 属性
			newCgm.entryInChunks = new Set(); // 初始化 entryInChunks 集合
		}
		for (const chunk of oldCgm.entryInChunks) { // 遍历旧模块是入口模块的 chunk 集合
			const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 对应的 ChunkGraphChunk
			const old = /** @type {Entrypoint} */ (cgc.entryModules.get(oldModule)); // 获取旧模块对应的入口配置
			/** @type {Map<Module, Entrypoint>} */
			const newEntryModules = new Map(); // 创建新的 entryModules 映射
			for (const [m, cg] of cgc.entryModules) { // 遍历原有的 entryModules 映射
				if (m === oldModule) { // 如果是旧模块
					newEntryModules.set(newModule, old); // 替换为新模块
				} else {
					newEntryModules.set(m, cg); // 否则保持原样
				}
			}
			cgc.entryModules = newEntryModules; // 替换 entryModules
			newCgm.entryInChunks.add(chunk); // 将该 chunk 添加到新模块的 entryInChunks 集合
		}
		oldCgm.entryInChunks = undefined; // 清空旧模块的 entryInChunks
	}

	if (oldCgm.runtimeInChunks !== undefined) { // 如果旧模块被当作 runtime module 使用
		if (newCgm.runtimeInChunks === undefined) { // 如果新模块没有 runtimeInChunks 属性
			newCgm.runtimeInChunks = new Set(); // 初始化 runtimeInChunks 集合
		}
		for (const chunk of oldCgm.runtimeInChunks) { // 遍历旧模块所在的 runtime chunk
			const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 对应的 ChunkGraphChunk
			cgc.runtimeModules.delete(/** @type {RuntimeModule} */ (oldModule)); // 从 runtimeModules 中移除旧模块
			cgc.runtimeModules.add(/** @type {RuntimeModule} */ (newModule)); // 添加新模块
			newCgm.runtimeInChunks.add(chunk); // 将 chunk 添加到新模块的 runtimeInChunks

			if (
				cgc.fullHashModules !== undefined &&
				cgc.fullHashModules.has(/** @type {RuntimeModule} */ (oldModule))
			) { // 如果 chunk 的 fullHashModules 包含旧模块
				cgc.fullHashModules.delete(/** @type {RuntimeModule} */ (oldModule)); // 删除旧模块
				cgc.fullHashModules.add(/** @type {RuntimeModule} */ (newModule)); // 添加新模块
			}
			if (
				cgc.dependentHashModules !== undefined &&
				cgc.dependentHashModules.has(/** @type {RuntimeModule} */ (oldModule))
			) { // 如果 chunk 的 dependentHashModules 包含旧模块
				cgc.dependentHashModules.delete(
					/** @type {RuntimeModule} */ (oldModule)
				);
				cgc.dependentHashModules.add(
					/** @type {RuntimeModule} */ (newModule)
				);
			}
		}
		oldCgm.runtimeInChunks = undefined; // 清空旧模块的 runtimeInChunks
	}
}

/**
 * @param {Module} module the checked module
 * @param {Chunk} chunk the checked chunk
 * @returns {boolean} true, if the chunk contains the module
 */
isModuleInChunk(module, chunk) { // 判断模块是否存在于指定 chunk 中
	const cgc = this._getChunkGraphChunk(chunk); // 获取 chunk 对应的 ChunkGraphChunk
	return cgc.modules.has(module); // 判断模块集合中是否包含该模块
}

/**
 * @param {Module} module the checked module
 * @param {ChunkGroup} chunkGroup the checked chunk group
 * @returns {boolean} true, if the chunk contains the module
 */
isModuleInChunkGroup(module, chunkGroup) { // 判断模块是否存在于指定 ChunkGroup 中
	for (const chunk of chunkGroup.chunks) { // 遍历 chunkGroup 中的所有 chunk
		if (this.isModuleInChunk(module, chunk)) return true; // 只要有一个 chunk 包含模块则返回 true
	}
	return false; // 所有 chunk 都不包含模块则返回 false
}

/**
 * @param {Module} module the checked module
 * @returns {boolean} true, if the module is entry of any chunk
 */
isEntryModule(module) { // 判断模块是否为入口模块
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule
	return cgm.entryInChunks !== undefined; // 判断是否存在 entryInChunks 集合
}

/**
 * @param {Module} module the module
 * @returns {Iterable<Chunk>} iterable of chunks (do not modify)
 */
getModuleChunksIterable(module) { // 获取模块所在的 chunk 集合(可迭代)
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule
	return cgm.chunks; // 返回 chunks 集合
}

/**
 * @param {Module} module the module
 * @param {function(Chunk, Chunk): -1|0|1} sortFn sort function
 * @returns {Iterable<Chunk>} iterable of chunks (do not modify)
 */
getOrderedModuleChunksIterable(module, sortFn) { // 获取排序后的模块 chunk 集合(可迭代)
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule
	cgm.chunks.sortWith(sortFn); // 使用传入的排序函数进行排序
	return cgm.chunks; // 返回排序后的 chunks 集合
}

/**
 * @param {Module} module the module
 * @returns {Chunk[]} array of chunks (cached, do not modify)
 */
getModuleChunks(module) { // 获取模块所在的 chunk 数组(使用缓存)
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule
	return cgm.chunks.getFromCache(getArray); // 从缓存中获取数组结果
}

/**
 * @param {Module} module the module
 * @returns {number} the number of chunk which contain the module
 */
getNumberOfModuleChunks(module) { // 获取包含该模块的 chunk 数量
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule
	return cgm.chunks.size; // 返回 chunk 集合的大小
}

/**
 * @param {Module} module the module
 * @returns {RuntimeSpecSet} runtimes
 */
getModuleRuntimes(module) { // 获取模块关联的运行时环境集合
	const cgm = this._getChunkGraphModule(module); // 获取 ChunkGraphModule
	return cgm.chunks.getFromUnorderedCache(getModuleRuntimes); // 从缓存中获取运行时集合
}

/**
 * @param {Chunk} chunk the chunk
 * @returns {number} the number of modules which are contained in this chunk
 */
getNumberOfChunkModules(chunk) { // 获取 chunk 中的模块数量
	const cgc = this._getChunkGraphChunk(chunk); // 获取 ChunkGraphChunk
	return cgc.modules.size; // 返回模块集合大小
}

/**
 * @param {Chunk} chunk the chunk
 * @returns {number} the number of full hash modules which are contained in this chunk
 */
getNumberOfChunkFullHashModules(chunk) { // 获取参与 chunk full hash 的模块数量
	const cgc = this._getChunkGraphChunk(chunk); // 获取 ChunkGraphChunk
	return cgc.fullHashModules === undefined ? 0 : cgc.fullHashModules.size; // 返回集合大小或 0
}

/**
 * @param {Chunk} chunk the chunk
 * @returns {Iterable<Module>} return the modules for this chunk
 */
getChunkModulesIterable(chunk) { // 获取 chunk 中模块的可迭代集合
	const cgc = this._getChunkGraphChunk(chunk); // 获取 ChunkGraphChunk
	return cgc.modules; // 返回模块集合
}

/**
 * @param {Chunk} chunk the chunk
 * @param {string} sourceType source type
 * @returns {Iterable<Module> | undefined} return the modules for this chunk
 */
getChunkModulesIterableBySourceType(chunk, sourceType) { // 获取指定 sourceType 的模块集合
	const cgc = this._getChunkGraphChunk(chunk); // 获取 ChunkGraphChunk
	const modulesWithSourceType = cgc.modules // 从缓存中获取按 sourceType 分类的模块集合
		.getFromUnorderedCache(cgc._modulesBySourceType)
		.get(sourceType); // 获取指定类型的模块集合
	return modulesWithSourceType; // 返回结果
}