cleanupForCache()
:
- 作用:清理缓存时调用的清理方法。遍历所有恢复的不安全缓存条目,执行相关的清理操作,包括清除模块的 Chunk 图、模块图,并执行模块的自清理操作。
js
// 清理缓存时需要调用的清理方法
cleanupForCache() {
// 遍历所有恢复的不安全缓存条目,执行相关清理操作
for (const module of this._restoredUnsafeCacheEntries) {
ChunkGraph.clearChunkGraphForModule(module); // 清除模块的 Chunk 图
ModuleGraph.clearModuleGraphForModule(module); // 清除模块的模块图
module.cleanupForCache(); // 执行模块自身的清理操作
}
}
create(data, callback)
:
- 作用 :创建模块。通过处理依赖项,解析请求,并根据解析结果生成模块。它依赖
beforeResolve
和factorize
钩子函数,执行相应的钩子操作,最终返回包含模块、文件依赖、缺失依赖等信息的结果。
js
/**
* 创建模块的工厂方法
* @param {ModuleFactoryCreateData} data 数据对象
* @param {function((Error | null)=, ModuleFactoryResult=): void} callback 回调函数
* @returns {void}
*/
create(data, callback) {
// 获取依赖项数组
const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
const context = data.context || this.context; // 上下文
const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS; // 解析选项
const dependency = dependencies[0]; // 获取第一个依赖项
const request = dependency.request; // 依赖的请求
const assertions = dependency.assertions; // 依赖的断言
const dependencyType = dependency.category || ""; // 依赖的类型
const contextInfo = data.contextInfo; // 上下文信息
// 文件依赖、缺失依赖、上下文依赖集合
const fileDependencies = new LazySet();
const missingDependencies = new LazySet();
const contextDependencies = new LazySet();
/** @type {ResolveData} */
const resolveData = {
contextInfo,
resolveOptions,
context,
request,
assertions,
dependencies,
dependencyType,
fileDependencies,
missingDependencies,
contextDependencies,
createData: {},
cacheable: true // 缓存标志
};
// 调用 beforeResolve 钩子
this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
if (err) {
// 如果出现错误,返回并且传递依赖项
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: false
});
}
// 如果结果为 false,表示忽略此模块
if (result === false) {
/** @type {ModuleFactoryResult} */
const factoryResult = {
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: resolveData.cacheable
};
if (resolveData.ignoredModule) {
factoryResult.module = resolveData.ignoredModule;
}
return callback(null, factoryResult);
}
// 如果结果为对象,则抛出错误
if (typeof result === "object")
throw new Error(
deprecationChangedHookMessage(
"beforeResolve",
this.hooks.beforeResolve
)
);
// 调用 factorize 钩子生成模块
this.hooks.factorize.callAsync(resolveData, (err, module) => {
if (err) {
// 如果出现错误,返回并且传递依赖项
return callback(err, {
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: false
});
}
/** @type {ModuleFactoryResult} */
const factoryResult = {
module,
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: resolveData.cacheable
};
callback(null, factoryResult);
});
});
}
resolveResource(contextInfo, context, unresolvedResource, resolver, resolveContext, callback)
:
- 作用 :解析资源。使用给定的解析器
resolver
来解析资源路径。如果解析失败,调用_resolveResourceErrorHints
方法生成错误提示,并在解析成功后返回解析结果。
js
/**
* 解析资源方法
* @param {ModuleFactoryCreateDataContextInfo} contextInfo 上下文信息
* @param {string} context 上下文
* @param {string} unresolvedResource 未解析的资源
* @param {ResolverWithOptions} resolver 解析器
* @param {ResolveContext} resolveContext 解析上下文
* @param {(err: null | Error, res?: string | false, req?: ResolveRequest) => void} callback 回调函数
*/
resolveResource(
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
callback
) {
// 调用解析器的 resolve 方法解析资源
resolver.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource, resolvedResourceResolveData) => {
if (err) {
// 如果解析失败,调用 _resolveResourceErrorHints 生成错误提示
return this._resolveResourceErrorHints(
err,
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
(err2, hints) => {
if (err2) {
err.message += `\n发生了解析附加提示时的致命错误:${err2.message}`;
err.stack += `\n发生了解析附加提示时的致命错误:\n${err2.stack}`;
return callback(err);
}
if (hints && hints.length > 0) {
err.message += `\n${hints.join("\n\n")}`;
}
// 检查扩展名是否缺少前导点(例如 "js" 而不是 ".js")
let appendResolveExtensionsHint = false;
const specifiedExtensions = Array.from(
resolver.options.extensions
);
const expectedExtensions = specifiedExtensions.map(extension => {
if (LEADING_DOT_EXTENSION_REGEX.test(extension)) {
appendResolveExtensionsHint = true;
return `.${extension}`;
}
return extension;
});
if (appendResolveExtensionsHint) {
err.message += `\n你是不是在 'resolve.extensions' 中遗漏了前导点?你是否打算将 '${JSON.stringify(
expectedExtensions
)}' 替换为 '${JSON.stringify(specifiedExtensions)}'?`;
}
callback(err);
}
);
}
// 返回解析的结果
callback(err, resolvedResource, resolvedResourceResolveData);
}
);
}
_resolveResourceErrorHints(error, contextInfo, context, unresolvedResource, resolver, resolveContext, callback)
:
- 作用:当解析失败时,生成详细的错误提示。它通过多个回调异步地提供错误解决的建议,比如缺少扩展名、错误的资源路径或强制扩展名的配置问题等。
js
/**
* 解析资源错误时生成提示信息
* @param {Error} error 错误信息
* @param {ModuleFactoryCreateDataContextInfo} contextInfo 上下文信息
* @param {string} context 上下文
* @param {string} unresolvedResource 未解析的资源
* @param {ResolverWithOptions} resolver 解析器
* @param {ResolveContext} resolveContext 解析上下文
* @param {Callback<string[]>} callback 回调函数
* @private
*/
_resolveResourceErrorHints(
error,
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
callback
) {
// 异步执行多个回调并获取错误提示
asyncLib.parallel(
[
callback => {
if (!resolver.options.fullySpecified) return callback();
// 在 fullySpecified 为 false 的情况下,尝试重新解析资源
resolver
.withOptions({
fullySpecified: false
})
.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource) => {
if (!err && resolvedResource) {
const resource = parseResource(resolvedResource).path.replace(
/^.*[\\/]/,
""
);
return callback(
null,
`你是否想要使用 '${resource}'?
突破性更改:请求 '${unresolvedResource}' 失败,仅仅因为它被解析为完全指定的(可能是因为来源是严格的 EcmaScript 模块,如具有 javascript 类型的模块,'*.mjs' 文件,或包含 '"type": "module"' 的 '*.js' 文件)。
此请求需要附加扩展名才能完全指定。`
);
}
callback();
}
);
},
callback => {
if (!resolver.options.enforceExtension) return callback();
// 在 enforceExtension 为 false 的情况下,尝试重新解析资源
resolver
.withOptions({
enforceExtension: false,
extensions: []
})
.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource) => {
if (!err && resolvedResource) {
let hint = "";
const match = /(\.[^.]+)(\?|$)/.exec(unresolvedResource);
if (match) {
const fixedRequest = unresolvedResource.replace(
/(\.[^.]+)(\?|$)/,
"$2"
);
hint = resolver.options.extensions.has(match[1])
? `你是否想使用 '${fixedRequest}'?`
: `你是否想使用 '${fixedRequest}'? 另请注意, '${match[1]}' 还未添加到 'resolve.extensions' 中,需将其添加进来以使其生效。`;
} else {
hint =
"你是否打算省略扩展名或移除 'resolve.enforceExtension' 设置?";
}
return callback(
null,
`请求 '${unresolvedResource}' 失败,仅仅因为 'resolve.enforceExtension' 被启用。
${hint}
现在无法在请求中包含扩展名。你是否打算在请求中强制包括扩展名,并使用 'resolve.extensions: []' 来强制执行此操作?`
);
}
callback();
}
);
},
callback => {
// 解析路径中的相对请求
if (
/^\.\.?\//.test(unresolvedResource) ||
resolver.options.preferRelative
) {
return callback();
}
resolver.resolve(
contextInfo,
context,
`./${unresolvedResource}`,
resolveContext,
(err, resolvedResource) => {
if (err || !resolvedResource) return callback();
const moduleDirectories = resolver.options.modules
.map(m => (Array.isArray(m) ? m.join(", ") : m))
.join(", ");
callback(
null,
`你是否想使用 './${unresolvedResource}'?
请求应该以 './' 开头,表示在当前目录解析。
如果不能更改源代码,你还可以使用 'preferRelative' 选项尝试在当前目录解析这些请求。`
);
}
);
}
],
(err, hints) => {
if (err) return callback(err);
callback(null, /** @type {string[]} */(hints).filter(Boolean));
}
);
}
getParser(type, parserOptions = EMPTY_PARSER_OPTIONS)
:
- 作用 :获取解析器。根据类型
type
和解析器选项parserOptions
,从缓存中获取或创建一个新的解析器。通过缓存机制优化性能,避免重复创建解析器。
js
/**
* 解析请求数组
* @param {string} type 类型
* @param {ParserOptions} parserOptions 解析器选项
* @returns {Parser} 解析器
*/
getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) {
let cache = this.parserCache.get(type);
if (cache === undefined) {
cache = new WeakMap();
this.parserCache.set(type, cache);
}
let parser = cache.get(parserOptions);
if (parser === undefined) {
parser = this.createParser(type, parserOptions);
cache.set(parserOptions, parser);
}
return parser;
}