webpack 核心编译器 第三节

这一节中主要介绍了webpack 中的统计信息管理,日志获取以及模块构的添加以及缓存获取,之类的方法。

js 复制代码
	/**
 * 获取当前编译的统计信息对象。
 * @returns {Stats} 统计信息对象。
 */
	getStats() {
		return new Stats(this);
	}

	/**
	 * 创建并规范化统计选项。
	 * @param {string | boolean | StatsOptions | undefined} optionsOrPreset 统计选项或预设值。
	 *   - 如果是 `boolean`:
	 *     - `false` 变为 `{ preset: "none" }`
	 *     - `true` 变为 `{ preset: "normal" }`
	 *   - 如果是 `string`,转换为 `{ preset: optionsOrPreset }`
	 * @param {CreateStatsOptionsContext=} context 额外的上下文信息(可选)。
	 * @returns {NormalizedStatsOptions} 规范化的统计选项。
	 */
	createStatsOptions(optionsOrPreset, context = {}) {
		if (typeof optionsOrPreset === "boolean") {
			// 如果是布尔值,则转换为对应的预设值
			optionsOrPreset = {
				preset: optionsOrPreset === false ? "none" : "normal"
			};
		} else if (typeof optionsOrPreset === "string") {
			// 如果是字符串,转换为预设值对象
			optionsOrPreset = { preset: optionsOrPreset };
		}

		if (typeof optionsOrPreset === "object" && optionsOrPreset !== null) {
			// 创建一个浅拷贝对象,以确保包含原型链上的属性
			/** @type {Partial<NormalizedStatsOptions>} */
			const options = {};

			// 遍历所有属性进行拷贝
			// eslint-disable-next-line guard-for-in
			for (const key in optionsOrPreset) {
				options[key] = optionsOrPreset[/** @type {keyof StatsOptions} */ (key)];
			}

			// 触发 `statsPreset` 钩子,根据预设修改选项
			if (options.preset !== undefined) {
				this.hooks.statsPreset.for(options.preset).call(options, context);
			}

			// 触发 `statsNormalize` 钩子,对选项进行最终规范化处理
			this.hooks.statsNormalize.call(options, context);

			return /** @type {NormalizedStatsOptions} */ (options);
		}

		/** @type {Partial<NormalizedStatsOptions>} */
		const options = {};
		// 触发 `statsNormalize` 钩子,确保选项符合规范
		this.hooks.statsNormalize.call(options, context);

		return /** @type {NormalizedStatsOptions} */ (options);
	}

	/**
	 * 创建一个统计数据工厂对象。
	 * @param {NormalizedStatsOptions} options 统计选项。
	 * @returns {StatsFactory} 统计数据工厂对象,用于生成统计数据。
	 */
	createStatsFactory(options) {
		const statsFactory = new StatsFactory();
		// 触发 `statsFactory` 钩子,允许外部修改 `statsFactory`
		this.hooks.statsFactory.call(statsFactory, options);
		return statsFactory;
	}

	/**
	 * 创建一个统计数据打印对象。
	 * @param {NormalizedStatsOptions} options 统计选项。
	 * @returns {StatsPrinter} 统计数据打印对象,用于格式化和输出统计信息。
	 */
	createStatsPrinter(options) {
		const statsPrinter = new StatsPrinter();
		// 触发 `statsPrinter` 钩子,允许外部修改 `statsPrinter`
		this.hooks.statsPrinter.call(statsPrinter, options);
		return statsPrinter;
	}

	/**
	 * 获取缓存实例
	 * @param {string} name 缓存的名称
	 * @returns {CacheFacade} 返回缓存外观对象
	 */
	getCache(name) {
		return this.compiler.getCache(name);
	}

	/**
	 * 获取一个日志记录器
	 * @param {string | (function(): string)} name 日志记录器的名称,或返回名称的函数
	 * @returns {Logger} 具有指定名称的日志记录器
	 */
	getLogger(name) {
		if (!name) {
			throw new TypeError("Compilation.getLogger(name) called without a name");
		}
		/** @type {LogEntry[] | undefined} */
		let logEntries;

		return new Logger(
			(type, args) => {
				// 如果 `name` 是一个函数,则调用它获取日志名称
				if (typeof name === "function") {
					name = name();
					if (!name) {
						throw new TypeError(
							"Compilation.getLogger(name) called with a function not returning a name"
						);
					}
				}
				let trace;
				switch (type) {
					case LogType.warn:
					case LogType.error:
					case LogType.trace:
						// 生成错误堆栈跟踪信息,剔除加载器相关调用
						trace = ErrorHelpers.cutOffLoaderExecution(
                        /** @type {string} */(new Error("Trace").stack)
						).split("\n").slice(3);
						break;
				}
				/** @type {LogEntry} */
				const logEntry = {
					time: Date.now(),
					type,
					args,
					trace
				};

				// 触发 `log` 钩子,若未被拦截,则存储日志记录
				if (this.hooks.log.call(name, logEntry) === undefined) {
					if (
						logEntry.type === LogType.profileEnd &&
						typeof console.profileEnd === "function"
					) {
						console.profileEnd(
							`[${name}] ${/** @type {NonNullable<LogEntry["args"]>} */ (logEntry.args)[0]}`
						);
					}
					if (logEntries === undefined) {
						logEntries = this.logging.get(name);
						if (logEntries === undefined) {
							logEntries = [];
							this.logging.set(name, logEntries);
						}
					}
					logEntries.push(logEntry);
					if (
						logEntry.type === LogType.profile &&
						typeof console.profile === "function"
					) {
						console.profile(
							`[${name}] ${/** @type {NonNullable<LogEntry["args"]>} */ (logEntry.args)[0]}`
						);
					}
				}
			},
			// 获取子日志记录器
			childName => {
				if (typeof name === "function") {
					if (typeof childName === "function") {
						return this.getLogger(() => {
							if (typeof name === "function") {
								name = name();
								if (!name) {
									throw new TypeError(
										"Compilation.getLogger(name) called with a function not returning a name"
									);
								}
							}
							if (typeof childName === "function") {
								childName = childName();
								if (!childName) {
									throw new TypeError(
										"Logger.getChildLogger(name) called with a function not returning a name"
									);
								}
							}
							return `${name}/${childName}`;
						});
					}
					return this.getLogger(() => {
						if (typeof name === "function") {
							name = name();
							if (!name) {
								throw new TypeError(
									"Compilation.getLogger(name) called with a function not returning a name"
								);
							}
						}
						return `${name}/${childName}`;
					});
				}
				if (typeof childName === "function") {
					return this.getLogger(() => {
						if (typeof childName === "function") {
							childName = childName();
							if (!childName) {
								throw new TypeError(
									"Logger.getChildLogger(name) called with a function not returning a name"
								);
							}
						}
						return `${name}/${childName}`;
					});
				}
				return this.getLogger(`${name}/${childName}`);
			}
		);
	}

	/**
	 * 添加模块到编译中
	 * @param {Module} module 要添加的模块
	 * @param {ModuleCallback} callback 回调函数,返回编译中的模块(新模块或已存在模块)
	 * @returns {void}
	 */
	addModule(module, callback) {
		this.addModuleQueue.add(module, callback);
	}

	/**
	 * 内部方法:添加模块
	 * @param {Module} module 要添加的模块
	 * @param {ModuleCallback} callback 回调函数
	 * @returns {void}
	 */
	_addModule(module, callback) {
		const identifier = module.identifier();
		const alreadyAddedModule = this._modules.get(identifier);
		if (alreadyAddedModule) {
			return callback(null, alreadyAddedModule);
		}

		const currentProfile = this.profile
			? this.moduleGraph.getProfile(module)
			: undefined;
		if (currentProfile !== undefined) {
			currentProfile.markRestoringStart();
		}

		this._modulesCache.get(identifier, null, (err, cacheModule) => {
			if (err) return callback(new ModuleRestoreError(module, err));

			if (currentProfile !== undefined) {
				currentProfile.markRestoringEnd();
				currentProfile.markIntegrationStart();
			}

			if (cacheModule) {
				cacheModule.updateCacheModule(module);
				module = cacheModule;
			}
			this._modules.set(identifier, module);
			this.modules.add(module);

			if (this._backCompat)
				ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);

			if (currentProfile !== undefined) {
				currentProfile.markIntegrationEnd();
			}
			callback(null, module);
		});
	}

	/**
	 * 根据模块对象获取已存在的模块
	 * @param {Module} module 提供的模块
	 * @returns {Module} 该模块
	 */
	getModule(module) {
		const identifier = module.identifier();
		return /** @type {Module} */ (this._modules.get(identifier));
	}

	/**
	 * 根据标识符查找模块
	 * @param {string} identifier 模块的标识符
	 * @returns {Module|undefined} 返回模块,若不存在则返回 `undefined`
	 */
	findModule(identifier) {
		return this._modules.get(identifier);
	}

	/**
	 * 安排模块的构建
	 * @param {Module} module 需要构建的模块
	 * @param {ModuleCallback} callback 回调函数
	 * @returns {void}
	 */
	buildModule(module, callback) {
		this.buildQueue.add(module, callback);
	}

	/**
	 * 内部方法:构建模块
	 * @param {Module} module 需要构建的模块
	 * @param {ModuleCallback} callback 回调函数
	 * @returns {void}
	 */
	_buildModule(module, callback) {
		const currentProfile = this.profile
			? this.moduleGraph.getProfile(module)
			: undefined;
		if (currentProfile !== undefined) {
			currentProfile.markBuildingStart();
		}

		module.needBuild(
			{
				compilation: this,
				fileSystemInfo: this.fileSystemInfo,
				valueCacheVersions: this.valueCacheVersions
			},
			(err, needBuild) => {
				if (err) return callback(err);

				if (!needBuild) {
					if (currentProfile !== undefined) {
						currentProfile.markBuildingEnd();
					}
					this.hooks.stillValidModule.call(module);
					return callback();
				}

				this.hooks.buildModule.call(module);
				this.builtModules.add(module);
				module.build(
					this.options,
					this,
					this.resolverFactory.get("normal", module.resolveOptions),
					this.inputFileSystem,
					err => {
						if (currentProfile !== undefined) {
							currentProfile.markBuildingEnd();
						}
						if (err) {
							this.hooks.failedModule.call(module, err);
							return callback(err);
						}
						this.hooks.succeedModule.call(module);
						return callback();
					}
				);
			}
		);
	}
相关推荐
噶琪7 分钟前
理解《CSS世界》盒模型、流、布局
前端·css
袁煦丞14 分钟前
云端跳跃:在NAS上用cpolar重现马里奥的童趣时光
前端·程序员·远程工作
the_one14 分钟前
《Canvas 炫酷动态粒子连线:从零打造流动星空特效》
前端·javascript·css
杀死一只知更鸟debug26 分钟前
vue2,vue3,vue3 + vite 动态加载图片的方式
前端·javascript·vue.js
剪刀石头布啊41 分钟前
浏览器进程与事件循环
前端·浏览器
剪刀石头布啊42 分钟前
浏览器渲染原理
前端·浏览器
日记成书1 小时前
【HTML 基础教程】HTML 表格
前端·html
木木黄木木1 小时前
HTML5贪吃蛇游戏开发经验分享
前端·html·html5
无名之逆1 小时前
hyperlane:Rust HTTP 服务器开发的不二之选
服务器·开发语言·前端·后端·安全·http·rust
李鸿耀1 小时前
前端包管理工具演进史:从 npm 到 pnpm 的技术革新
前端·面试