webpack 格式化模块 第 四 节

这是 Webpack 中 NormalModule 的核心方法之一,负责执行模块构建,主要包括:

  1. 创建 loaderContext,为所有 loader 提供统一上下文环境;
  2. 调用 runLoaders 依次执行所有配置的 loader,对资源进行转换;
  3. 支持 readResource 钩子用于读取资源内容;
  4. 收集构建时产生的依赖(文件、目录、缺失、loader 本身);
  5. 处理构建结果(支持 SourceMap、AST、缓存等);
  6. 调用回调 callback 返回结果或错误。
js 复制代码
/**
 * @param {WebpackOptions} options webpack 配置项
 * @param {Compilation} compilation 当前 compilation 实例
 * @param {ResolverWithOptions} resolver 模块路径解析器
 * @param {InputFileSystem} fs 输入文件系统(用于读取文件)
 * @param {NormalModuleCompilationHooks} hooks 构建钩子集合
 * @param {function((WebpackError | null)=): void} callback 构建完成后的回调函数
 * @returns {void}
 */
_doBuild(options, compilation, resolver, fs, hooks, callback) {
	// 创建 loader 执行上下文,供后续所有 loader 使用
	const loaderContext = this._createLoaderContext(
		resolver,
		options,
		compilation,
		fs,
		hooks
	);

	// 定义 loader 执行的结果类型:[源码内容, sourceMap, 额外信息]
	/** @typedef {[string | Buffer, string | SourceMapSource, Record<string, any>]}  Result */

	/**
	 * 处理构建结果(包括错误和正常构建结果)
	 * @param {Error | null} err 错误信息
	 * @param {(Result | null)=} _result loader 处理结果
	 * @returns {void}
	 */
	const processResult = (err, _result) => {
		if (err) {
			// 如果错误不是 Error 实例,转换为 Webpack 自定义错误类型
			if (!(err instanceof Error)) {
				err = new NonErrorEmittedError(err);
			}
			// 获取当前执行的 loader(用于错误信息定位)
			const currentLoader = this.getCurrentLoader(loaderContext);
			const error = new ModuleBuildError(err, {
				from:
					currentLoader &&
					compilation.runtimeTemplate.requestShortener.shorten(
						currentLoader.loader
					)
			});
			// 通过回调将错误传出
			return callback(error);
		}

		// 强制类型转换
		const result = /** @type {Result} */ (_result);
		const source = result[0];
		const sourceMap = result.length >= 1 ? result[1] : null;
		const extraInfo = result.length >= 2 ? result[2] : null;

		// 如果最终结果既不是 string 也不是 Buffer,报错
		if (!Buffer.isBuffer(source) && typeof source !== "string") {
			const currentLoader = this.getCurrentLoader(loaderContext, 0);
			const err = new Error(
				`最终 loader (${
					currentLoader
						? compilation.runtimeTemplate.requestShortener.shorten(
								currentLoader.loader
							)
						: "unknown"
				}) 没有返回 Buffer 或 String`
			);
			const error = new ModuleBuildError(err);
			return callback(error);
		}

		// 是否为二进制模块
		const isBinaryModule =
			this.generatorOptions && this.generatorOptions.binary !== undefined
				? this.generatorOptions.binary
				: this.binary;

		// 创建模块源代码(最终产物)
		this._source = this.createSource(
			/** @type {string} */ (options.context),
			isBinaryModule ? asBuffer(source) : asString(source),
			sourceMap,
			compilation.compiler.root
		);

		// 清除旧的 source size 缓存
		if (this._sourceSizes !== undefined) this._sourceSizes.clear();

		// 如果提供 AST,则保存(供后续优化、分析用)
		this._ast =
			typeof extraInfo === "object" &&
			extraInfo !== null &&
			extraInfo.webpackAST !== undefined
				? extraInfo.webpackAST
				: null;

		// 成功回调
		return callback();
	};

	// 构建信息对象
	const buildInfo = /** @type {BuildInfo} */ (this.buildInfo);
	buildInfo.fileDependencies = new LazySet(); // 依赖的文件
	buildInfo.contextDependencies = new LazySet(); // 上下文依赖
	buildInfo.missingDependencies = new LazySet(); // 缺失依赖
	buildInfo.cacheable = true; // 默认构建结果是可缓存的

	// 执行构建前钩子(beforeLoaders)
	try {
		hooks.beforeLoaders.call(
			this.loaders,
			this,
			/** @type {LoaderContext<any>} */ (loaderContext)
		);
	} catch (err) {
		processResult(/** @type {Error} */ (err));
		return;
	}

	// 如果存在 loaders,则初始化 buildDependencies
	if (this.loaders.length > 0) {
		/** @type {BuildInfo} */
		(this.buildInfo).buildDependencies = new LazySet();
	}

	// 调用 loader-runner 执行所有 loaders
	runLoaders(
		{
			resource: this.resource, // 模块路径
			loaders: this.loaders,   // 所有的 loader
			context: loaderContext,  // loader 上下文对象

			// 自定义资源加载逻辑(可被插件拦截)
			processResource: (loaderContext, resourcePath, callback) => {
				const resource = loaderContext.resource;
				const scheme = getScheme(resource); // 如 file://、http://
				hooks.readResource
					.for(scheme)
					.callAsync(loaderContext, (err, result) => {
						if (err) return callback(err);
						if (typeof result !== "string" && !result) {
							// 不支持的协议
							return callback(
								new UnhandledSchemeError(
									/** @type {string} */ (scheme),
									resource
								)
							);
						}
						return callback(null, result); // 返回原始源码内容
					});
			}
		},
		// 所有 loader 执行完成的回调
		(err, result) => {
			// 清理 loaderContext 上挂载的临时属性(避免泄漏)
			loaderContext._compilation =
				loaderContext._compiler =
				loaderContext._module =
				// eslint-disable-next-line no-warning-comments
				// @ts-ignore
				loaderContext.fs =
					undefined;

			// 没有结果,说明构建失败
			if (!result) {
				/** @type {BuildInfo} */
				(this.buildInfo).cacheable = false;
				return processResult(
					err || new Error("loader-runner 没有返回任何结果"),
					null
				);
			}

			const buildInfo = /** @type {BuildInfo} */ (this.buildInfo);

			// 合并 loader 收集到的依赖信息
			const fileDependencies =
				/** @type {NonNullable<KnownBuildInfo["fileDependencies"]>} */
				(buildInfo.fileDependencies);
			const contextDependencies =
				/** @type {NonNullable<KnownBuildInfo["contextDependencies"]>} */
				(buildInfo.contextDependencies);
			const missingDependencies =
				/** @type {NonNullable<KnownBuildInfo["missingDependencies"]>} */
				(buildInfo.missingDependencies);

			fileDependencies.addAll(result.fileDependencies);
			contextDependencies.addAll(result.contextDependencies);
			missingDependencies.addAll(result.missingDependencies);

			// 将每个 loader 的路径加入构建依赖
			for (const loader of this.loaders) {
				const buildDependencies =
					/** @type {NonNullable<KnownBuildInfo["buildDependencies"]>} */
					(buildInfo.buildDependencies);
				buildDependencies.add(loader.loader);
			}

			// 标记缓存性
			buildInfo.cacheable = buildInfo.cacheable && result.cacheable;

			// 处理 loader 的最终返回结果
			processResult(err, result.result);
		}
	);
}

markModuleAsErrored(error)

🌟功能总结:

该方法在构建模块过程中出现错误时调用,用来:

  1. 恢复模块上一次构建成功时的 buildMeta
  2. 将当前模块标记为构建失败;
  3. 将错误信息添加到模块的错误列表中。
js 复制代码
/**
 * 标记当前模块构建出错
 * @param {WebpackError} error 构建中出现的错误
 * @returns {void}
 */
markModuleAsErrored(error) {
	// 恢复上一次构建成功时的 buildMeta 信息
	this.buildMeta = { ...this._lastSuccessfulBuildMeta };
	this.error = error;
	this.addError(error);
}

applyNoParseRule(rule, content)

功能总结:

该方法用于根据 noParse 配置判断某个模块内容是否需要跳过解析(比如不执行 AST 解析以提升性能)。

支持三种规则类型:

  • 字符串:检查模块源码是否以该字符串开头;
  • 函数 :执行函数返回 true 表示跳过;
  • 正则表达式:测试是否匹配成功。
js 复制代码
/**
 * 应用 noParse 规则
 * @param {TODO} rule noParse 规则(可以是字符串、函数或正则表达式)
 * @param {string} content 模块源码内容
 * @returns {boolean} 是否不进行解析
 */
applyNoParseRule(rule, content) {
	if (typeof rule === "string") {
		return content.startsWith(rule); // 以该字符串开头就跳过解析
	}
	if (typeof rule === "function") {
		return rule(content); // 自定义函数返回 true 就跳过
	}
	return rule.test(content); // 正则匹配
}

shouldPreventParsing(noParseRule, request)

功能总结:

这是 applyNoParseRule 的封装,用于对模块请求路径判断是否应跳过解析

判断逻辑:

  1. 如果没有 noParseRule,则必须解析;
  2. 如果是单个规则(非数组),直接调用 applyNoParseRule
  3. 如果是数组规则,逐个尝试匹配,只要有一个匹配就跳过解析。
js 复制代码
/**
 * 判断模块是否应跳过解析
 * @param {TODO} noParseRule noParse 配置项
 * @param {string} request 请求路径
 * @returns {boolean} true 表示跳过解析,false 表示进行解析
 */
shouldPreventParsing(noParseRule, request) {
	if (!noParseRule) {
		return false;
	}
	if (!Array.isArray(noParseRule)) {
		return this.applyNoParseRule(noParseRule, request);
	}
	for (let i = 0; i < noParseRule.length; i++) {
		const rule = noParseRule[i];
		if (this.applyNoParseRule(rule, request)) {
			return true;
		}
	}
	return false;
}
相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax