webpack 格式化模块 第 五 节

_initBuildHash(compilation)

作用:

初始化模块的构建哈希值,作为当前模块内容的唯一标识。该哈希将用于缓存判断与增量构建。

逻辑概览:

  1. 使用配置中定义的哈希算法创建哈希对象。
  2. 如果模块已有 _source,将其内容更新进哈希。
  3. 将模块的 buildMeta 元信息序列化后加入哈希。
  4. 生成最终哈希字符串并赋值给 buildInfo.hash
js 复制代码
/**
 * 初始化构建哈希,用于标识模块的唯一内容版本
 * @param {Compilation} compilation compilation 对象
 * @private
 */
_initBuildHash(compilation) {
	const hash = createHash(
		/** @type {Algorithm} */
		(compilation.outputOptions.hashFunction) // 使用配置中指定的哈希算法
	);
	if (this._source) {
		hash.update("source");              // 标记源代码部分
		this._source.updateHash(hash);     // 用模块源代码更新哈希
	}
	hash.update("meta");                  // 标记元信息部分
	hash.update(JSON.stringify(this.buildMeta)); // 把 buildMeta 对象序列化后更新哈希
	/** @type {BuildInfo} */
	(this.buildInfo).hash = /** @type {string} */ (hash.digest("hex")); // 生成最终的 hex 字符串作为哈希
}

build(options, compilation, resolver, fs, callback)

作用:

模块的主构建流程,执行 loader、解析源码、生成依赖、创建快照等关键步骤。

逻辑概览:

  1. 初始化模块状态(清除旧数据:源码、错误、依赖、AST等)。

  2. 准备 buildMetabuildInfo 两个构建数据结构。

  3. 调用私有方法 _doBuild() 执行真正的构建。

  4. 构建完成后:

    • 调用 parser 对源码进行语法分析,提取依赖。
    • 执行相关钩子(如 beforeParse)。
    • 如果解析失败,记录错误并终止。
    • 排序依赖、生成构建哈希。
  5. 检查依赖是否都是绝对路径,若不是添加警告。

  6. 生成文件系统快照(用于缓存和监听)。

js 复制代码
/**
 * 模块构建主入口,执行模块的构建流程
 * @param {WebpackOptions} options webpack 配置对象
 * @param {Compilation} compilation 当前 compilation 实例
 * @param {ResolverWithOptions} resolver 模块解析器
 * @param {InputFileSystem} fs 文件系统对象
 * @param {function(WebpackError=): void} callback 构建完成回调
 * @returns {void}
 */
build(options, compilation, resolver, fs, callback) {
	this._forceBuild = false;
	this._source = null; // 清空上次构建的源码缓存
	if (this._sourceSizes !== undefined) this._sourceSizes.clear(); // 清除源码大小缓存
	this._sourceTypes = undefined;
	this._ast = null;
	this.error = null;

	// 清空构建错误和警告
	this.clearWarningsAndErrors();
	// 清除依赖项和 block 信息
	this.clearDependenciesAndBlocks();

	// 重置构建元信息对象
	this.buildMeta = {};

	// 初始化构建信息
	this.buildInfo = {
		cacheable: false,
		parsed: true,
		fileDependencies: undefined,
		contextDependencies: undefined,
		missingDependencies: undefined,
		buildDependencies: undefined,
		valueDependencies: undefined,
		hash: undefined,
		assets: undefined,
		assetsInfo: undefined
	};

	const startTime = compilation.compiler.fsStartTime || Date.now();

	// 获取构建相关钩子
	const hooks = NormalModule.getCompilationHooks(compilation);

	return this._doBuild(options, compilation, resolver, fs, hooks, err => {
		if (err) {
			// 构建失败,记录错误并初始化哈希
			this.markModuleAsErrored(err);
			this._initBuildHash(compilation);
			return callback();
		}

		/**
		 * 构建解析错误处理函数
		 * @param {Error} e 错误对象
		 */
		const handleParseError = e => {
			const source = /** @type {Source} */ (this._source).source(); // 获取源码
			const loaders = this.loaders.map(item =>
				contextify(
					/** @type {string} */ (options.context),
					item.loader,
					compilation.compiler.root
				)
			);
			const error = new ModuleParseError(source, e, loaders, this.type); // 创建解析错误对象
			this.markModuleAsErrored(error); // 标记模块构建失败
			this._initBuildHash(compilation); // 初始化构建哈希
			return callback();
		};

		/**
		 * 构建完成处理函数
		 */
		const handleBuildDone = () => {
			try {
				hooks.beforeSnapshot.call(this); // 执行构建快照前钩子
			} catch (err) {
				this.markModuleAsErrored(/** @type {WebpackError} */ (err));
				return callback();
			}

			const snapshotOptions = compilation.options.snapshot.module;
			const { cacheable } = /** @type {BuildInfo} */ (this.buildInfo);

			// 如果模块不可缓存或未配置快照,直接回调
			if (!cacheable || !snapshotOptions) {
				return callback();
			}

			/** @type {undefined | Set<string>} */
			let nonAbsoluteDependencies;

			/**
			 * 检查依赖集合中的路径是否为绝对路径
			 * 若非绝对路径,尝试转换为绝对路径并加入警告
			 * @param {LazySet<string>} deps 依赖集合
			 */
			const checkDependencies = deps => {
				for (const dep of deps) {
					if (!ABSOLUTE_PATH_REGEX.test(dep)) {
						if (nonAbsoluteDependencies === undefined)
							nonAbsoluteDependencies = new Set();
						nonAbsoluteDependencies.add(dep);
						deps.delete(dep);
						try {
							const depWithoutGlob = dep.replace(/[\\/]?\*.*$/, "");
							const absolute = join(
								compilation.fileSystemInfo.fs,
								/** @type {string} */ (this.context),
								depWithoutGlob
							);
							if (absolute !== dep && ABSOLUTE_PATH_REGEX.test(absolute)) {
								(depWithoutGlob !== dep
									? /** @type {NonNullable<KnownBuildInfo["contextDependencies"]>} */
										(
											/** @type {BuildInfo} */ (this.buildInfo)
												.contextDependencies
										)
									: deps
								).add(absolute);
							}
						} catch (_err) {
							// 忽略路径转换错误
						}
					}
				}
			};

			const buildInfo = /** @type {BuildInfo} */ (this.buildInfo);
			const fileDependencies = /** @type {NonNullable<KnownBuildInfo["fileDependencies"]>} */ (buildInfo.fileDependencies);
			const contextDependencies = /** @type {NonNullable<KnownBuildInfo["contextDependencies"]>} */ (buildInfo.contextDependencies);
			const missingDependencies = /** @type {NonNullable<KnownBuildInfo["missingDependencies"]>} */ (buildInfo.missingDependencies);

			checkDependencies(fileDependencies);
			checkDependencies(missingDependencies);
			checkDependencies(contextDependencies);

			// 如果存在非绝对路径,添加警告
			if (nonAbsoluteDependencies !== undefined) {
				const InvalidDependenciesModuleWarning = getInvalidDependenciesModuleWarning();
				this.addWarning(
					new InvalidDependenciesModuleWarning(this, nonAbsoluteDependencies)
				);
			}

			// 创建文件系统快照,后续用于缓存判断和监听
			compilation.fileSystemInfo.createSnapshot(
				startTime,
				fileDependencies,
				contextDependencies,
				missingDependencies,
				snapshotOptions,
				(err, snapshot) => {
					if (err) {
						this.markModuleAsErrored(err);
						return;
					}
					// 快照完成后清除原始依赖信息
					buildInfo.fileDependencies = undefined;
					buildInfo.contextDependencies = undefined;
					buildInfo.missingDependencies = undefined;
					buildInfo.snapshot = snapshot;
					return callback();
				}
			);
		};

		/**
		 * 解析完成后处理函数
		 */
		const handleParseResult = () => {
			// 对模块依赖排序
			this.dependencies.sort(
				concatComparators(
					compareSelect(a => a.loc, compareLocations),
					keepOriginalOrder(this.dependencies)
				)
			);
			this._initBuildHash(compilation); // 生成构建哈希
			this._lastSuccessfulBuildMeta = /** @type {BuildMeta} */ (this.buildMeta); // 缓存元信息
			return handleBuildDone();
		};

		// 解析前钩子
		try {
			hooks.beforeParse.call(this);
		} catch (err) {
			this.markModuleAsErrored(/** @type {WebpackError} */ (err));
			this._initBuildHash(compilation);
			return callback();
		}

		// 判断是否禁止解析模块
		const noParseRule = options.module && options.module.noParse;
		if (this.shouldPreventParsing(noParseRule, this.request)) {
			/** @type {BuildInfo} */ (this.buildInfo).parsed = false;
			this._initBuildHash(compilation);
			return handleBuildDone();
		}

		// 尝试使用 parser 解析模块源码
		try {
			const source = /** @type {Source} */ (this._source).source();
			/** @type {Parser} */ (this.parser).parse(this._ast || source, {
				source,
				current: this,
				module: this,
				compilation,
				options
			});
		} catch (parseErr) {
			handleParseError(/** @type {Error} */ (parseErr));
			return;
		}

		// 解析完成
		handleParseResult();
	});
}

getConcatenationBailoutReason(context)

作用:

用于模块串联(scope hoisting)优化时判断模块是否能被内联。

逻辑概览:

  1. 交由当前模块使用的 generator 判断是否支持串联。
  2. 如果不能串联,返回一个具体的阻止原因字符串。
js 复制代码
/**
 * 获取不能进行模块串联优化的原因
 * @param {ConcatenationBailoutReasonContext} context 优化上下文
 * @returns {string | undefined} 返回阻止串联的原因或 undefined(表示可以串联)
 */
getConcatenationBailoutReason(context) {
	return /** @type {Generator} */ (
		this.generator
	).getConcatenationBailoutReason(this, context);
}

getSideEffectsConnectionState(moduleGraph)

作用:

用于 tree-shaking,判断该模块是否含有副作用(side effects)。

逻辑概览:

  1. 优先从 factoryMetabuildMeta 判断是否声明了无副作用。
  2. 如果声明为无副作用,则递归检查其依赖的副作用状态。
  3. 如果任意依赖有副作用,则本模块也视为有副作用。
  4. 缓存和标记模块是否添加过优化提示(避免重复)。
js 复制代码
/**
 * 判断模块在仅被副作用引用时是否应该被保留
 * @param {ModuleGraph} moduleGraph 模块图
 * @returns {ConnectionState} 连接状态
 */
getSideEffectsConnectionState(moduleGraph) {
	if (this.factoryMeta !== undefined) {
		if (this.factoryMeta.sideEffectFree) return false;
		if (this.factoryMeta.sideEffectFree === false) return true;
	}
	if (this.buildMeta !== undefined && this.buildMeta.sideEffectFree) {
		if (this._isEvaluatingSideEffects)
			return ModuleGraphConnection.CIRCULAR_CONNECTION;

		this._isEvaluatingSideEffects = true;
		/** @type {ConnectionState} */
		let current = false;

		for (const dep of this.dependencies) {
			const state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
			if (state === true) {
				// 添加优化提示
				if (
					this._addedSideEffectsBailout === undefined
						? ((this._addedSideEffectsBailout = new WeakSet()), true)
						: !this._addedSideEffectsBailout.has(moduleGraph)
				) {
					this._addedSideEffectsBailout.add(moduleGraph);
					moduleGraph
						.getOptimizationBailout(this)
						.push(() => `Dependency (${dep.type}) with side effects at ${formatLocation(dep.loc)}`);
				}
				this._isEvaluatingSideEffects = false;
				return true;
			} else if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
				current = ModuleGraphConnection.addConnectionStates(current, state);
			}
		}

		this._isEvaluatingSideEffects = false;
		return current;
	}
	return true;
}

getSourceTypes()

作用:

获取模块生成的资源类型(如 'javascript''css' 等)。

逻辑概览:

  1. _sourceTypes 缓存不存在,则调用 generator.getTypes() 获取类型集合。
  2. 返回类型集合(通常是 Set 类型)。
js 复制代码
/**
 * 获取模块生成的源代码类型(如 javascript、css 等)
 * @returns {SourceTypes} 类型集合(不可修改)
 */
getSourceTypes() {
	if (this._sourceTypes === undefined) {
		this._sourceTypes = /** @type {Generator} */ (this.generator).getTypes(this);
	}
	return this._sourceTypes;
}
相关推荐
掘金安东尼12 分钟前
🧭 前端周刊第409期(2025年4月7日-13日)
前端·javascript·面试
凌览17 分钟前
密码太多记不住?用Trae开发一个密码管理插件
前端·后端·trae
墨渊君18 分钟前
React Native 与 React(Web) 开发的不同点, 如何快速上手?
前端·javascript·react native
林太白20 分钟前
企业级NestJS如何创建项目学起来
前端·vue.js·后端
橙某人20 分钟前
横向图片选择器之自动滚动定位功能-Javascript、Vue
前端·javascript·vue.js
无奈何杨24 分钟前
策略规则指标字段引用关系与快捷跳转
前端·后端
MurphyChen25 分钟前
告别等待!后端推送前端数据技术大盘点
前端·后端·面试
蘑菇头爱平底锅28 分钟前
数字孪生-DTS-孪创城市-前端用代码实现经济态势
前端·数据可视化
这里有鱼汤28 分钟前
用 Python 的 playwright 库 爬取 Pexels 网站的图片
前端·后端·python
旭久36 分钟前
html简易实现推箱子小游戏原理(易上手)
前端·javascript·html