_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
:当入口项处理完成后执行的回调函数。
实现逻辑
-
从
options
获取name
,然后在this.entries
查找对应的entryData
,如果name
未定义,则使用this.globalEntry
。 -
如果
entryData
为空,则初始化:dependencies
和includeDependencies
作为空数组。options
继承EntryOptions
并保留name
。- 在
entryData[target]
数组中添加entry
。 - 将
entryData
存入this.entries
,键为name
。
-
如果
entryData
已存在:- 直接将
entry
添加到entryData[target]
。 - 遍历
options
,如果新选项与已有选项冲突,则回调WebpackError
并返回。
- 直接将
-
触发
this.hooks.addEntry
钩子。 -
调用
addModuleTree
构建该入口:-
传入
context
、entry
和contextInfo
(如果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
:当模块重新编译完成时执行的回调函数。
实现逻辑
- 直接将
module
和callback
添加到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
:当模块重新编译完成后执行的回调函数。
实现逻辑
-
触发
this.hooks.rebuildModule
钩子。 -
备份模块的
dependencies
和blocks
,以便后续恢复引用。 -
调用
invalidateBuild()
使模块失效,并通知buildQueue
重新编译该模块。 -
执行
buildModule()
进行编译:-
如果编译失败,触发
this.hooks.finishRebuildingModule
并执行callback(err)
。 -
如果编译成功:
-
使
processDependenciesQueue
失效,并解冻moduleGraph
。 -
解析模块的依赖项
processModuleDependencies()
:- 若解析失败,执行
callback(err)
。 - 若解析成功,调用
removeReasonsOfDependencyBlock()
移除旧依赖的引用。
- 若解析失败,执行
-
触发
this.hooks.finishRebuildingModule
钩子,并执行callback(null, module)
。
-
-
这些方法主要涉及 Webpack 的 模块管理 和 增量构建 (HMR 可能会用到)。_addEntryItem
负责管理入口项,而 _rebuildModule
负责增量编译模块,从而提高 Webpack 的构建效率。