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;
}
相关推荐
deli00711 分钟前
1 个汉字算几个 token?实测中英文 prompt 与代码的编码差异
前端
无限聚核18 分钟前
抖音 certificate.prepare 之后怎么验券?和美团 msconsume 差在哪,无限聚核
前端·微信小程序
2603_9658966227 分钟前
Grid 网格布局|CSS 二维页面排版体系
前端·css
有味道的男人43 分钟前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft1 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly2 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝2 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦2 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡2 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI2 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战