webpack 核心编译器 第五节

_addEntryItem 方法

该方法用于添加一个入口项(entry item),并触发相应的 Webpack 钩子。

js 复制代码
	/**
	 * 添加一个入口项,并触发相应的 Webpack 钩子
	 *
	 * @param {string} context 入口的上下文路径
	 * @param {Dependency} entry 入口依赖对象,Webpack 需要跟踪它
	 * @param {"dependencies" | "includeDependencies"} target 入口依赖的类型
	 * @param {EntryOptions} options 入口选项
	 * @param {ModuleCallback} callback 处理完成后的回调函数
	 * @returns {void}
	 */
	_addEntryItem(context, entry, target, options, callback) {
		// 从 options 中获取 entry 的名称
		const { name } = options;

		// 根据名称获取 entryData,如果名称未定义,则使用全局 entry
		let entryData = name !== undefined ? this.entries.get(name) : this.globalEntry;

		// 如果 entryData 不存在,初始化一个 entryData 对象
		if (entryData === undefined) {
			entryData = {
				dependencies: [], // 依赖项数组
				includeDependencies: [], // 额外包含的依赖项数组
				options: {
					name: undefined, // 默认 name 为 undefined
					...options // 继承传入的 options
				}
			};

			// 将当前 entry 添加到对应的依赖数组
			entryData[target].push(entry);

			// 将 entryData 存入 entries 映射表
			this.entries.set(
				/** @type {NonNullable<EntryOptions["name"]>} */(name),
				entryData
			);
		} else {
			// 如果 entryData 已存在,则直接添加 entry
			entryData[target].push(entry);

			// 遍历 options,检查是否存在冲突
			for (const key of Object.keys(options)) {
				// 如果选项值为 undefined,跳过
				if (options[key] === undefined) continue;

				// 如果选项值与已存在的相同,跳过
				if (entryData.options[key] === options[key]) continue;

				// 如果选项值是数组,并且内容相同,跳过
				if (
					Array.isArray(entryData.options[key]) &&
					Array.isArray(options[key]) &&
					arrayEquals(entryData.options[key], options[key])
				) {
					continue;
				}

				// 如果 entryData.options[key] 为空,则直接赋值
				if (entryData.options[key] === undefined) {
					entryData.options[key] = options[key];
				} else {
					// 发现冲突,返回错误
					return callback(
						new WebpackError(
							`Conflicting entry option ${key} = ${entryData.options[key]} vs ${options[key]}`
						)
					);
				}
			}
		}

		// 触发 addEntry 钩子
		this.hooks.addEntry.call(entry, options);

		// 构建 entry 依赖树
		this.addModuleTree(
			{
				context,
				dependency: entry,
				contextInfo: entryData.options.layer
					? { issuerLayer: entryData.options.layer }
					: undefined
			},
			(err, module) => {
				// 如果出错,触发 failedEntry 钩子并返回错误
				if (err) {
					this.hooks.failedEntry.call(entry, options, err);
					return callback(err);
				}

				// 构建成功,触发 succeedEntry 钩子
				this.hooks.succeedEntry.call(entry, options, module);
				return callback(null, module);
			}
		);
	}

参数

  • context:入口的上下文路径(context path)。
  • entry:表示入口的 Dependency 对象,Webpack 需要跟踪它。
  • target:指定依赖项的类型,可以是 "dependencies""includeDependencies"
  • options:包含入口选项的 EntryOptions 对象。
  • callback:当入口项处理完成后执行的回调函数。

实现逻辑

  1. options 获取 name,然后在 this.entries 查找对应的 entryData,如果 name 未定义,则使用 this.globalEntry

  2. 如果 entryData 为空,则初始化:

    • dependenciesincludeDependencies 作为空数组。
    • options 继承 EntryOptions 并保留 name
    • entryData[target] 数组中添加 entry
    • entryData 存入 this.entries,键为 name
  3. 如果 entryData 已存在:

    • 直接将 entry 添加到 entryData[target]
    • 遍历 options,如果新选项与已有选项冲突,则回调 WebpackError 并返回。
  4. 触发 this.hooks.addEntry 钩子。

  5. 调用 addModuleTree 构建该入口:

    • 传入 contextentrycontextInfo(如果 layer 选项存在)。

    • 处理回调:

      • 如果发生错误,触发 this.hooks.failedEntry 并执行 callback(err)
      • 否则,触发 this.hooks.succeedEntry 并执行 callback(null, module)

rebuildModule 方法

用于重新编译一个模块,将其添加到 rebuildQueue 任务队列。

js 复制代码
	/**
	 * 重新编译一个模块(异步队列处理)
	 *
	 * @param {Module} module 需要重新构建的模块
	 * @param {ModuleCallback} callback 当模块重新编译完成时调用的回调
	 * @returns {void}
	 */
	rebuildModule(module, callback) {
		// 将模块加入重新构建的任务队列
		this.rebuildQueue.add(module, callback);
	}

参数

  • module:需要重新构建的 Module 实例。
  • callback:当模块重新编译完成时执行的回调函数。

实现逻辑

  1. 直接将 modulecallback 添加到 rebuildQueue 队列中。

_rebuildModule 方法

内部方法,用于实际执行模块的重新构建。

js 复制代码
/**
	 * 重新编译一个模块(实际的编译逻辑)
	 *
	 * @param {Module} module 需要重新构建的模块
	 * @param {ModuleCallback} callback 当模块重新编译完成时调用的回调
	 * @returns {void}
	 */
	_rebuildModule(module, callback) {
		// 触发 rebuildModule 钩子
		this.hooks.rebuildModule.call(module);

		// 备份旧的依赖项和代码块
		const oldDependencies = module.dependencies.slice();
		const oldBlocks = module.blocks.slice();

		// 使模块的构建无效,以便重新编译
		module.invalidateBuild();

		// 让 buildQueue 重新编译该模块
		this.buildQueue.invalidate(module);

		// 执行模块的构建
		this.buildModule(module, err => {
			if (err) {
				// 构建失败,触发 finishRebuildingModule 钩子并返回错误
				return this.hooks.finishRebuildingModule.callAsync(module, err2 => {
					if (err2) {
						callback(
							makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule")
						);
						return;
					}
					callback(err);
				});
			}

			// 使 processDependenciesQueue 失效,准备处理依赖
			this.processDependenciesQueue.invalidate(module);

			// 解冻模块图
			this.moduleGraph.unfreeze();

			// 处理模块的依赖项
			this.processModuleDependencies(module, err => {
				if (err) return callback(err);

				// 移除旧的依赖项
				this.removeReasonsOfDependencyBlock(module, {
					dependencies: oldDependencies,
					blocks: oldBlocks
				});

				// 触发 finishRebuildingModule 钩子
				this.hooks.finishRebuildingModule.callAsync(module, err2 => {
					if (err2) {
						callback(
							makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule")
						);
						return;
					}
					// 重新编译完成,执行回调
					callback(null, module);
				});
			});
		});
	}

参数

  • module:需要重新编译的 Module 实例。
  • callback:当模块重新编译完成后执行的回调函数。

实现逻辑

  1. 触发 this.hooks.rebuildModule 钩子。

  2. 备份模块的 dependenciesblocks,以便后续恢复引用。

  3. 调用 invalidateBuild() 使模块失效,并通知 buildQueue 重新编译该模块。

  4. 执行 buildModule() 进行编译:

    • 如果编译失败,触发 this.hooks.finishRebuildingModule 并执行 callback(err)

    • 如果编译成功:

      1. 使 processDependenciesQueue 失效,并解冻 moduleGraph

      2. 解析模块的依赖项 processModuleDependencies()

        • 若解析失败,执行 callback(err)
        • 若解析成功,调用 removeReasonsOfDependencyBlock() 移除旧依赖的引用。
      3. 触发 this.hooks.finishRebuildingModule 钩子,并执行 callback(null, module)


这些方法主要涉及 Webpack 的 模块管理增量构建 (HMR 可能会用到)。_addEntryItem 负责管理入口项,而 _rebuildModule 负责增量编译模块,从而提高 Webpack 的构建效率。

相关推荐
_未知_开摆7 分钟前
uniapp APP端在线升级(简版)
开发语言·前端·javascript·vue.js·uni-app
sen_shan19 分钟前
Vue3+Vite+TypeScript+Element Plus开发-02.Element Plus安装与配置
前端·javascript·typescript·vue3·element·element plus
疾风铸境31 分钟前
Qt5.14.2+mingw64编译OpenCV3.4.14一次成功记录
前端·webpack·node.js
晓风伴月35 分钟前
Css:overflow: hidden截断条件‌及如何避免截断
前端·css·overflow截断条件
最新资讯动态38 分钟前
使用“一次开发,多端部署”,实现Pura X阔折叠的全新设计
前端
爱泡脚的鸡腿1 小时前
HTML CSS 第二次笔记
前端·css
灯火不休ᝰ1 小时前
前端处理pdf文件流,展示pdf
前端·pdf
智践行1 小时前
Trae开发实战之转盘小程序
前端·trae
最新资讯动态1 小时前
DialogHub上线OpenHarmony开源社区,高效开发鸿蒙应用弹窗
前端
lvbb661 小时前
框架修改思路
前端·javascript·vue.js