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 的构建效率。

相关推荐
mldong26 分钟前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排1 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马6 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端