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