这段代码是 Webpack 中模块系统的基类 Module
的实现,提供了模块在整个构建生命周期中的核心功能接口。它定义了一套通用的逻辑,用于管理模块的构建状态、错误和警告收集、模块间依赖关系分析、哈希更新,以及可访问性和可选性判断。许多功能在构建过程、优化阶段(如 Tree-shaking)、模块缓存判断等场景中扮演重要角色。
首先,Module
类提供了完整的错误与警告管理方法,例如 addWarning
、addError
、getWarnings
、getErrors
等,能够记录并查询模块在构建过程中的诊断信息。通过这些接口,Webpack 能够在编译报告中准确输出每个模块的构建问题。
其次,类中包含多个判断模块是否被其他模块依赖的方法,如 hasReasons
和 isOptional
,这对于优化和判断模块是否可以被移除至关重要。同时,它还提供了模块与 chunk 或 chunkGroup 的可访问性分析方法,例如 isAccessibleInChunk
和 hasReasonForChunk
,用于判断模块是否需要包含在特定输出中,是代码分割和动态导入机制的基础。
在构建方面,Module
提供了 needBuild
和 needRebuild
方法,用于判断模块是否需要重新构建。模块更新时的哈希计算逻辑由 updateHash
提供,这有助于判断模块是否发生了变化,从而支持缓存机制和增量构建。
此外,该类还定义了一些必须由子类实现的抽象方法,如 identifier
、readableIdentifier
和 build
,以支持不同类型模块的具体逻辑扩展。整个 Module
基类为 Webpack 的模块机制提供了统一的行为定义和扩展接口,是模块构建系统的核心组成部分。
js
/**
* 添加一个警告信息。
* 如果 _warnings 还未初始化,则先初始化为一个空数组。
* 然后将传入的 warning 添加到该数组中。
* @param {WebpackError} warning 警告对象
*/
addWarning(warning) {
if (this._warnings === undefined) {
this._warnings = [];
}
this._warnings.push(warning);
}
/**
* 获取当前模块的所有警告信息。
* @returns {Iterable<WebpackError> | undefined} 警告数组或 undefined(如果没有)
*/
getWarnings() {
return this._warnings;
}
/**
* 获取当前模块的警告数量。
* @returns {number} 警告数
*/
getNumberOfWarnings() {
return this._warnings !== undefined ? this._warnings.length : 0;
}
/**
* 添加一个错误信息。
* 如果 _errors 还未初始化,则先初始化为一个空数组。
* 然后将传入的 error 添加到该数组中。
* @param {WebpackError} error 错误对象
*/
addError(error) {
if (this._errors === undefined) {
this._errors = [];
}
this._errors.push(error);
}
/**
* 获取当前模块的所有错误信息。
* @returns {Iterable<WebpackError> | undefined} 错误数组或 undefined(如果没有)
*/
getErrors() {
return this._errors;
}
/**
* 获取当前模块的错误数量。
* @returns {number} 错误数
*/
getNumberOfErrors() {
return this._errors !== undefined ? this._errors.length : 0;
}
/**
* 清空当前模块的所有错误和警告信息。
*/
clearWarningsAndErrors() {
if (this._warnings !== undefined) {
this._warnings.length = 0;
}
if (this._errors !== undefined) {
this._errors.length = 0;
}
}
/**
* 判断当前模块是否是"可选的"。
* 仅当所有的 incoming 连接的依赖都是 optional 且激活时才算可选。
* @param {ModuleGraph} moduleGraph 模块图
* @returns {boolean} 是否为可选模块
*/
isOptional(moduleGraph) {
let hasConnections = false;
for (const r of moduleGraph.getIncomingConnections(this)) {
if (
!r.dependency ||
!r.dependency.optional ||
!r.isTargetActive(undefined)
) {
return false;
}
hasConnections = true;
}
return hasConnections;
}
/**
* 判断模块是否在给定 chunk 中是可访问的(可排除某个 chunk)。
* @param {ChunkGraph} chunkGraph chunk 图
* @param {Chunk} chunk 当前 chunk
* @param {Chunk=} ignoreChunk 要忽略的 chunk(可选)
* @returns {boolean} 是否可访问
*/
isAccessibleInChunk(chunkGraph, chunk, ignoreChunk) {
for (const chunkGroup of chunk.groupsIterable) {
if (!this.isAccessibleInChunkGroup(chunkGraph, chunkGroup)) return false;
}
return true;
}
/**
* 判断模块是否在某个 chunkGroup 中是可访问的(可排除某个 chunk)。
* 采用广度优先队列遍历 chunk group 的所有父级,直到找到包含此模块的 chunk。
* @param {ChunkGraph} chunkGraph chunk 图
* @param {ChunkGroup} chunkGroup chunk 组
* @param {Chunk=} ignoreChunk 要忽略的 chunk(可选)
* @returns {boolean} 是否可访问
*/
isAccessibleInChunkGroup(chunkGraph, chunkGroup, ignoreChunk) {
const queue = new Set([chunkGroup]);
queueFor: for (const cg of queue) {
for (const chunk of cg.chunks) {
if (chunk !== ignoreChunk && chunkGraph.isModuleInChunk(this, chunk))
continue queueFor;
}
if (chunkGroup.isInitial()) return false;
for (const parent of chunkGroup.parentsIterable) queue.add(parent);
}
return true;
}
/**
* 判断模块是否有"理由"存在于某个 chunk 中。
* 一般用于 chunk 拆分或 tree-shaking 的逻辑。
* @param {Chunk} chunk 当前 chunk
* @param {ModuleGraph} moduleGraph 模块图
* @param {ChunkGraph} chunkGraph chunk 图
* @returns {boolean} 是否有理由包含在该 chunk 中
*/
hasReasonForChunk(chunk, moduleGraph, chunkGraph) {
for (const [
fromModule,
connections
] of moduleGraph.getIncomingConnectionsByOriginModule(this)) {
if (!connections.some(c => c.isTargetActive(chunk.runtime))) continue;
for (const originChunk of chunkGraph.getModuleChunksIterable(
/** @type {Module} */ (fromModule)
)) {
if (!this.isAccessibleInChunk(chunkGraph, originChunk, chunk))
return true;
}
}
return false;
}
/**
* 判断当前模块是否被任何其它模块依赖(即是否"有理由"存在)。
* 常用于标记无依赖模块是否可删除。
* @param {ModuleGraph} moduleGraph 模块图
* @param {RuntimeSpec} runtime 当前运行时
* @returns {boolean} 是否被依赖
*/
hasReasons(moduleGraph, runtime) {
for (const c of moduleGraph.getIncomingConnections(this)) {
if (c.isTargetActive(runtime)) return true;
}
return false;
}
/**
* 返回模块的调试信息字符串。
* @returns {string} 形如 `Module[debugId: identifier()]`
*/
toString() {
return `Module[${this.debugId}: ${this.identifier()}]`;
}
/**
* 判断是否需要重新构建模块(异步回调方式)。
* @param {NeedBuildContext} context 构建上下文
* @param {function((WebpackError | null)=, boolean=): void} callback 回调,返回是否需要构建
*/
needBuild(context, callback) {
callback(
null,
!this.buildMeta ||
this.needRebuild === Module.prototype.needRebuild ||
deprecatedNeedRebuild(this, context)
);
}
/**
* (已废弃)判断是否需要重新构建模块(同步方式)。
* 始终返回 true。
* @deprecated 使用 needBuild 替代
*/
needRebuild(fileTimestamps, contextTimestamps) {
return true;
}
/**
* 更新模块的 hash 值,用于依赖追踪与缓存验证。
* @param {Hash} hash 哈希对象
* @param {UpdateHashContext} context 上下文信息(包括 chunkGraph 和 runtime)
*/
updateHash(
hash,
context = {
chunkGraph: ChunkGraph.getChunkGraphForModule(
this,
"Module.updateHash",
"DEP_WEBPACK_MODULE_UPDATE_HASH"
),
runtime: undefined
}
) {
const { chunkGraph, runtime } = context;
hash.update(chunkGraph.getModuleGraphHash(this, runtime));
if (this.presentationalDependencies !== undefined) {
for (const dep of this.presentationalDependencies) {
dep.updateHash(hash, context);
}
}
super.updateHash(hash, context);
}
/**
* 使模块失效,强制重新构建。
* 默认实现为空,应由子类覆盖。
*/
invalidateBuild() {
// should be overridden to support this feature
}
/**
* 抽象方法:返回模块的唯一标识符。
* 由子类实现。未实现时抛出异常。
* @abstract
*/
identifier() {
const AbstractMethodError = require("./AbstractMethodError");
throw new AbstractMethodError();
}
/**
* 抽象方法:返回模块用户可读的标识符(短路径)。
* 由子类实现。未实现时抛出异常。
* @abstract
*/
readableIdentifier(requestShortener) {
const AbstractMethodError = require("./AbstractMethodError");
throw new AbstractMethodError();
}
/**
* 抽象方法:构建模块。
* 由子类实现。未实现时抛出异常。
* @abstract
*/
build(options, compilation, resolver, fs, callback) {
const AbstractMethodError = require("./AbstractMethodError");
throw new AbstractMethodError();
}