webpack 格式化模块 第 六 节

codeGeneration(...)

功能总结:

用于执行模块的代码生成阶段,将构建后的模块内容根据不同的 sourceType(如 javascript, css 等)生成最终用于输出的代码,处理构建错误、收集 runtime 依赖,并封装为 CodeGenerationResult 返回。

核心逻辑:

  • 如果模块尚未被解析,添加基础的 runtime 依赖;
  • 遍历所有 sourceType,通过模块的 generator 来生成对应源码;
  • 对每个生成的源码进行缓存包装;
  • 返回包含所有生成源码及 runtime 依赖的结果对象。
js 复制代码
/**
 * 生成模块的代码
 * 
 * @param {CodeGenerationContext} context - 代码生成的上下文环境,包含依赖模板、运行时代码模板、模块图、chunk 图等
 * @returns {CodeGenerationResult} - 返回代码生成的结果,包括不同类型源码及运行时需求
 */
codeGeneration({
	dependencyTemplates,
	runtimeTemplate,
	moduleGraph,
	chunkGraph,
	runtime,
	concatenationScope,
	codeGenerationResults,
	sourceTypes
}) {
	/** @type {Set<string>} */
	const runtimeRequirements = new Set();

	// 获取构建信息中是否包含解析后的内容
	const { parsed } = /** @type {BuildInfo} */ (this.buildInfo);

	// 如果没有解析过,说明模块内容需要基本的运行时支持
	if (!parsed) {
		runtimeRequirements.add(RuntimeGlobals.module);
		runtimeRequirements.add(RuntimeGlobals.exports);
		runtimeRequirements.add(RuntimeGlobals.thisAsExports);
	}

	/** 用于获取 code generation 阶段使用的临时数据 */
	const getData = () => this._codeGeneratorData;

	const sources = new Map();

	// 遍历所有 source 类型(如 javascript、css),生成对应源码
	for (const type of sourceTypes || chunkGraph.getModuleSourceTypes(this)) {
		const source = this.error
			// 如果模块构建报错,返回一个抛出错误的源码片段
			? new RawSource(
					`throw new Error(${JSON.stringify(this.error.message)});`
				)
			: /** 使用模块的 generator 来生成代码 */
				/** @type {Generator} */ (this.generator).generate(this, {
					dependencyTemplates,
					runtimeTemplate,
					moduleGraph,
					chunkGraph,
					runtimeRequirements,
					runtime,
					concatenationScope,
					codeGenerationResults,
					getData,
					type
				});

		// 缓存生成结果
		if (source) {
			sources.set(type, new CachedSource(source));
		}
	}

	// 返回代码生成结果
	const resultEntry = {
		sources,
		runtimeRequirements,
		data: this._codeGeneratorData
	};
	return resultEntry;
}

originalSource()

功能总结:

返回该模块最初的、未经 webpack 转换的源码。

核心逻辑:

  • 返回构建前保存的 _source 字段,通常是 RawSource 类型。
js 复制代码
/**
 * 获取模块在被 webpack 转换前的原始源码
 * 
 * @returns {Source | null}
 */
originalSource() {
	return this._source;
}

invalidateBuild()

功能总结:

标记模块为"强制需要重建"。

核心逻辑:

  • 设置内部标志 _forceBuild = true,强制跳过缓存检查,触发重新构建。
js 复制代码
/**
 * 强制标记该模块下次构建时必须重建
 * 
 * @returns {void}
 */
invalidateBuild() {
	this._forceBuild = true;
}

needBuild(context, callback)

功能总结:

判断模块是否需要重新构建,是增量构建(cache)中非常关键的逻辑。

核心逻辑:

  • 若标记了强制构建或模块出错,返回需构建;
  • 如果模块不可缓存或没有快照,返回需构建;
  • 对比 valueDependenciesvalueCacheVersions 是否有变动;
  • 调用 fileSystemInfo 检查文件快照是否仍有效;
  • 最后触发 needBuild 钩子供其他插件判断是否需要构建。
js 复制代码
/**
 * 判断当前模块是否需要重新构建
 * 
 * @param {NeedBuildContext} context - 构建判断所需的上下文信息(包含文件快照、缓存版本等)
 * @param {function((WebpackError | null)=, boolean=): void} callback - 回调函数,返回是否需要重新构建
 * @returns {void}
 */
needBuild(context, callback) {
	const { fileSystemInfo, compilation, valueCacheVersions } = context;

	// 如果设置了强制构建,则直接返回 true
	if (this._forceBuild) return callback(null, true);

	// 如果之前构建报错,也需要重新构建
	if (this.error) return callback(null, true);

	const { cacheable, snapshot, valueDependencies } =
		/** @type {BuildInfo} */ (this.buildInfo);

	// 如果模块不可缓存,则每次都需要构建
	if (!cacheable) return callback(null, true);

	// 没有快照信息说明模块状态未知,也需要重新构建
	if (!snapshot) return callback(null, true);

	// 判断值依赖是否发生变化
	if (valueDependencies) {
		if (!valueCacheVersions) return callback(null, true);
		for (const [key, value] of valueDependencies) {
			if (value === undefined) return callback(null, true);
			const current = valueCacheVersions.get(key);
			if (
				value !== current &&
				(typeof value === "string" ||
					typeof current === "string" ||
					current === undefined ||
					!isSubset(value, current))
			) {
				return callback(null, true);
			}
		}
	}

	// 校验文件快照是否仍然有效
	fileSystemInfo.checkSnapshotValid(snapshot, (err, valid) => {
		if (err) return callback(err);
		if (!valid) return callback(null, true);

		// 调用 compilation 钩子判断是否需要构建
		const hooks = NormalModule.getCompilationHooks(compilation);
		hooks.needBuild.callAsync(this, context, (err, needBuild) => {
			if (err) {
				return callback(
					HookWebpackError.makeWebpackError(
						err,
						"NormalModule.getCompilationHooks().needBuild"
					)
				);
			}
			callback(null, Boolean(needBuild));
		});
	});
}

size(type)

功能总结:

返回该模块某个类型源码的大小估算值,用于 chunk 拆分、优化等场景。

核心逻辑:

  • 优先从缓存中获取;
  • 如果没有,则通过 generator 的 getSize 方法动态计算;
  • 结果缓存后返回,保证下次访问高效。
js 复制代码
/**
 * 获取模块的大小估算值(某个 source 类型)
 * 
 * @param {string=} type - 指定的源码类型(如 javascript)
 * @returns {number} - 返回模块估算大小(最小为 1)
 */
size(type) {
	const cachedSize =
		this._sourceSizes === undefined ? undefined : this._sourceSizes.get(type);
	if (cachedSize !== undefined) {
		return cachedSize;
	}

	// 使用 generator 获取模块的大小
	const size = Math.max(
		1,
		/** @type {Generator} */ (this.generator).getSize(this, type)
	);

	// 缓存计算结果
	if (this._sourceSizes === undefined) {
		this._sourceSizes = new Map();
	}
	this._sourceSizes.set(type, size);
	return size;
}

addCacheDependencies(...)

功能总结:

向 webpack 缓存系统添加当前模块的所有构建依赖项,用于确保依赖变更时可以触发重新构建。

核心逻辑:

  • 如果有构建快照,则使用快照提供的依赖集合;
  • 否则回退到旧字段(如 fileDependencies);
  • 最后合并所有 buildDependencies(如 loader/plugin 产生的构建依赖)。
js 复制代码
/**
 * 添加模块构建依赖信息到对应集合中
 * 
 * @param {LazySet<string>} fileDependencies - 文件依赖集合
 * @param {LazySet<string>} contextDependencies - 目录依赖集合
 * @param {LazySet<string>} missingDependencies - 缺失依赖集合
 * @param {LazySet<string>} buildDependencies - 构建依赖集合
 */
addCacheDependencies(
	fileDependencies,
	contextDependencies,
	missingDependencies,
	buildDependencies
) {
	const { snapshot, buildDependencies: buildDeps } =
		/** @type {BuildInfo} */ (this.buildInfo);

	if (snapshot) {
		// 从快照中恢复依赖
		fileDependencies.addAll(snapshot.getFileIterable());
		contextDependencies.addAll(snapshot.getContextIterable());
		missingDependencies.addAll(snapshot.getMissingIterable());
	} else {
		// 如果没有 snapshot,使用旧字段恢复依赖
		const {
			fileDependencies: fileDeps,
			contextDependencies: contextDeps,
			missingDependencies: missingDeps
		} = /** @type {BuildInfo} */ (this.buildInfo);
		if (fileDeps !== undefined) fileDependencies.addAll(fileDeps);
		if (contextDeps !== undefined) contextDependencies.addAll(contextDeps);
		if (missingDeps !== undefined) missingDependencies.addAll(missingDeps);
	}

	if (buildDeps !== undefined) {
		buildDependencies.addAll(buildDeps);
	}
}

updateHash(hash, context)

功能总结:

更新当前模块的哈希,用于内容变更检测及构建缓存标识。

核心逻辑:

  • 使用模块构建信息中的哈希作为一部分输入;
  • 使用模块的 generator 更新哈希;
  • 调用父类方法补充其他必要信息。
js 复制代码
/**
 * 更新模块的 hash 值,用于标识模块内容变更
 * 
 * @param {Hash} hash - 当前使用的 hash 对象
 * @param {UpdateHashContext} context - hash 更新所需上下文(包含 chunkGraph、runtime 等)
 * @returns {void}
 */
updateHash(hash, context) {
	// 加入构建信息中的 hash 值
	hash.update(/** @type {BuildInfo} */ (this.buildInfo).hash);

	// 使用 generator 更新 hash
	/** @type {Generator} */
	(this.generator).updateHash(hash, {
		module: this,
		...context
	});

	// 调用父类逻辑(可能加入依赖信息等)
	super.updateHash(hash, context);
}
相关推荐
_一条咸鱼_9 分钟前
深入解析 Vue API 模块原理:从基础到源码的全方位探究(八)
前端·javascript·面试
患得患失94921 分钟前
【前端】【难点】前端富文本开发的核心难点总结与思路优化
前端·富文本
执键行天涯24 分钟前
在vue项目中package.json中的scripts 中 dev:“xxx“中的xxx什么概念
前端·vue.js·json
雯0609~36 分钟前
html:文件上传-一次性可上传多个文件,将文件展示到页面(可删除
前端·html
涵信41 分钟前
2024年React最新高频面试题及核心考点解析,涵盖基础、进阶和新特性,助你高效备战
前端·react.js·前端框架
mmm.c43 分钟前
应对多版本vue,nvm,node,npm,yarn的使用
前端·vue.js·npm
混血哲谈1 小时前
全新电脑如何快速安装nvm,npm,pnpm
前端·npm·node.js
天天扭码1 小时前
项目登录注册页面太丑?试试我“仿制”的丝滑页面(全源码可复制)
前端·css·html
桂月二二1 小时前
Vue3服务端渲染深度实战:SSR架构优化与企业级应用
前端·vue.js·架构
萌萌哒草头将军1 小时前
🚀🚀🚀 这六个事半功倍的 Pinia 库,你一定要知道!
前端·javascript·vue.js