这段代码为 Webpack 或类似工具的模块加载系统的核心部分,提供了强大的扩展性和配置灵活性。通过钩子机制,它允许开发者在模块解析、构建、加载、生成等各个阶段插入自定义逻辑,以满足不同的构建需求。
1. 构造函数参数
context
: 模块解析的上下文(如当前工作目录)。fs
: 用于文件操作的文件系统对象。resolverFactory
: 解析器工厂,用于创建各种解析器。options
: 配置选项,包括默认规则、规则集、解析器选项、生成器选项等。associatedObjectForCache
: 用于缓存相关操作的对象。layers
: 是否启用层级功能。
2. 钩子定义
resolve
: 解析模块的钩子,处理模块解析的结果。resolveForScheme
,resolveInScheme
: 钩子映射,用于根据不同的 Scheme(资源解析方案)处理解析数据。factorize
: 用于模块的因式分解,创建模块实例。beforeResolve
,afterResolve
: 在模块解析之前和之后执行的钩子。createModule
: 根据解析数据创建模块的钩子。module
: 用于修改已创建模块的钩子。createParser
,parser
: 解析器相关的钩子。createGenerator
,generator
: 生成器相关的钩子。createModuleClass
: 根据解析数据创建模块类的钩子。
3. 主要功能
- 解析模块 :通过
resolve
钩子和resolverFactory
,对模块资源进行解析,支持多种方案(如不同的加载器、解析器等)。 - 模块因式分解 :通过
factorize
钩子,根据解析的数据创建模块实例。 - 解析与加载器的关系:构造函数内部会根据加载器规则,处理不同类型的加载器(如预加载器、普通加载器、后加载器等)。
- 规则集编译与应用:编译并应用规则集(包括默认规则和用户自定义规则),用于处理不同资源的加载方式。
- 资源与依赖管理:管理文件依赖、缺失依赖、上下文依赖等,确保在模块解析过程中正确处理。
4. 复杂的资源解析流程
- 解析过程中考虑了不同的资源类型、请求路径、上下文路径等,并支持通过各种钩子进行扩展。
- 钩子的执行顺序、阶段设置(如
stage
)允许在不同的生命周期阶段执行特定的操作。 - 针对每种资源类型(如 JavaScript、CSS 等),系统通过不同的钩子和规则进行相应的处理。
5. 模块创建与修改
- 在模块解析完成后,使用
createModule
钩子根据解析数据创建模块实例。 - 通过
module
钩子,允许在模块创建后进一步修改其属性或行为。 - 通过
createModuleClass
钩子,支持根据解析数据创建不同的模块类。
6. 缓存机制
- 使用
parserCache
和generatorCache
缓存解析器和生成器,优化模块的处理性能。 - 通过
associatedObjectForCache
对象,缓存解析和生成过程中的数据,避免重复计算。
7. 异步与回调管理
- 代码中大量使用异步钩子(如
AsyncSeriesBailHook
)和回调函数(如callback
),确保在模块解析和创建过程中能够处理异步操作,保证流程的顺利进行。
8. 错误处理与异常管理
- 代码中通过多次检查并返回错误(如解析失败、未提供必需的参数等)确保异常的处理,防止出现不符合预期的行为。
js
/**
* @param {object} param 参数
* @param {string=} param.context 上下文
* @param {InputFileSystem} param.fs 文件系统
* @param {ResolverFactory} param.resolverFactory 解析器工厂
* @param {ModuleOptions} param.options 模块选项
* @param {object} param.associatedObjectForCache 缓存将附加到的对象
* @param {boolean=} param.layers 启用层级
*/
constructor({
context,
fs,
resolverFactory,
options,
associatedObjectForCache,
layers = false
}) {
super(); // 调用父类构造函数,初始化基类
this.hooks = Object.freeze({
/**
* @type {AsyncSeriesBailHook<[ResolveData], Module | false | void>}
* 用于解析模块的钩子。可以返回一个 Module 实例,或者返回 false 表示忽略该解析,或者返回 void。
*/
resolve: new AsyncSeriesBailHook(["resolveData"]),
/**
* @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>}
* 钩子映射,针对不同的 scheme 解析资源数据。返回 true 或 void。
*/
resolveForScheme: new HookMap(
() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
),
/**
* @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>}
* 与 `resolveForScheme` 相似,但用于特定的 scheme 上下文。
*/
resolveInScheme: new HookMap(
() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
),
/**
* @type {AsyncSeriesBailHook<[ResolveData], Module | undefined>}
* 用于模块的因式分解钩子。返回 Module 实例或 undefined。
*/
factorize: new AsyncSeriesBailHook(["resolveData"]),
/**
* @type {AsyncSeriesBailHook<[ResolveData], false | void>}
* 在解析模块之前调用的钩子。可以返回 false 以阻止解析过程。
*/
beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
/**
* @type {AsyncSeriesBailHook<[ResolveData], false | void>}
* 在解析模块之后调用的钩子。可以返回 false 以停止进一步的处理。
*/
afterResolve: new AsyncSeriesBailHook(["resolveData"]),
/**
* @type {AsyncSeriesBailHook<[ResolveData["createData"], ResolveData], Module | void>}
* 用于根据解析数据创建模块的钩子。
*/
createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
/**
* @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData]>}
* 用于修改已创建模块的钩子,模块已解析并传递了工厂数据。
*/
module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
/**
* @type {HookMap<SyncBailHook<[ParserOptions], Parser | void>>}
* 钩子映射,用于创建解析器。如果解析器创建失败,则返回 void。
*/
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
/**
* @type {HookMap<SyncBailHook<[TODO, ParserOptions], void>>}
* 用于修改解析器的钩子,解析器创建之后执行。
*/
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
/**
* @type {HookMap<SyncBailHook<[GeneratorOptions], Generator | void>>}
* 钩子映射,用于创建生成器。返回生成器实例或 void 表示没有生成。
*/
createGenerator: new HookMap(
() => new SyncBailHook(["generatorOptions"])
),
/**
* @type {HookMap<SyncBailHook<[TODO, GeneratorOptions], void>>}
* 用于修改生成器的钩子,生成器创建之后执行。
*/
generator: new HookMap(
() => new SyncHook(["generator", "generatorOptions"])
),
/**
* @type {HookMap<SyncBailHook<[TODO, ResolveData], Module | void>>}
* 钩子映射,用于根据解析数据创建模块类。
*/
createModuleClass: new HookMap(
() => new SyncBailHook(["createData", "resolveData"])
)
});
this.resolverFactory = resolverFactory; // 初始化模块解析器工厂
this.ruleSet = ruleSetCompiler.compile([ // 编译规则集,包括默认规则和用户定义的规则
{
rules: options.defaultRules
},
{
rules: options.rules
}
]);
this.context = context || ""; // 设置模块解析的上下文(目录)
this.fs = fs; // 设置文件系统,用于文件解析
this._globalParserOptions = options.parser; // 存储全局的解析器选项
this._globalGeneratorOptions = options.generator; // 存储全局的生成器选项
/** @type {Map<string, WeakMap<object, Parser>>} */
this.parserCache = new Map(); // 缓存不同类型资源的解析器
/** @type {Map<string, WeakMap<object, Generator>>} */
this.generatorCache = new Map(); // 缓存不同类型资源的生成器
/** @type {Set<Module>} */
this._restoredUnsafeCacheEntries = new Set(); // 存储已恢复的模块
// 绑定并缓存解析资源
const cacheParseResource = parseResource.bindCache(
associatedObjectForCache
);
const cachedParseResourceWithoutFragment =
parseResourceWithoutFragment.bindCache(associatedObjectForCache);
this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment;
// 因式分解钩子,通过解析数据创建模块
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory", // 为此钩子命名,便于识别
stage: 100 // 设置钩子链中的执行阶段
},
(resolveData, callback) => {
// 首先通过 `resolve` 钩子解析数据
this.hooks.resolve.callAsync(resolveData, (err, result) => {
if (err) return callback(err); // 处理解析时的错误
// 如果返回值是 false,表示忽略该解析
if (result === false) return callback();
// 如果返回值是 Module 实例,立即返回该模块
if (result instanceof Module) return callback(null, result);
// 如果返回的是对象(但不是 Module),则抛出弃用的错误
if (typeof result === "object")
throw new Error(
`${deprecationChangedHookMessage(
"resolve",
this.hooks.resolve
)} Returning a Module object will result in this module used as result.`
);
// 调用 `afterResolve` 钩子,在解析后执行
this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
if (err) return callback(err); // 处理 afterResolve 钩子中的错误
// 如果返回值是对象,表示弃用,抛出错误
if (typeof result === "object")
throw new Error(
deprecationChangedHookMessage(
"afterResolve",
this.hooks.afterResolve
)
);
// 如果返回值是 false,表示忽略该解析
if (result === false) return callback();
const createData = resolveData.createData;
// 使用 `createModule` 钩子创建模块
this.hooks.createModule.callAsync(
createData,
resolveData,
(err, createdModule) => {
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
// 如果未创建模块,使用模块类创建
createdModule = this.hooks.createModuleClass
.for(
/** @type {ModuleSettings} */
(createData.settings).type
)
.call(createData, resolveData);
// 如果没有模块类,则默认使用 NormalModule 创建模块
if (!createdModule) {
createdModule = /** @type {Module} */ (
new NormalModule(
/** @type {NormalModuleCreateData} */
(createData)
)
);
}
}
// 应用 `module` 钩子,修改模块后再返回
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
// 最后返回创建的模块
return callback(null, createdModule);
}
);
});
});
}
);
// 注册 resolve 钩子,处理模块资源解析
this.hooks.resolve.tapAsync(
{
name: "NormalModuleFactory", // 钩子的名称
stage: 100 // 钩子的执行阶段,值越小,越早执行
},
(data, callback) => {
const {
contextInfo,
context,
dependencies,
dependencyType,
request,
assertions,
resolveOptions,
fileDependencies,
missingDependencies,
contextDependencies
} = data; // 解构获取传入的数据
const loaderResolver = this.getResolver("loader"); // 获取 loader 解析器
let matchResourceData; // 匹配的资源数据
let unresolvedResource; // 未解析的资源
let elements; // loader 元素
let noPreAutoLoaders = false; // 是否没有预加载器
let noAutoLoaders = false; // 是否没有加载器
let noPrePostAutoLoaders = false; // 是否没有预后加载器
const contextScheme = getScheme(context); // 获取上下文的 Scheme
let scheme = getScheme(request); // 获取请求的 Scheme
if (!scheme) {
let requestWithoutMatchResource = request;
const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request); // 匹配资源
if (matchResourceMatch) {
let matchResource = matchResourceMatch[1];
if (matchResource.charCodeAt(0) === 46) { // 检查是否以 . 或 ../ 开头
const secondChar = matchResource.charCodeAt(1);
if (secondChar === 47 || (secondChar === 46 && matchResource.charCodeAt(2) === 47)) {
matchResource = join(this.fs, context, matchResource); // 处理相对路径
}
}
matchResourceData = {
resource: matchResource,
...cacheParseResource(matchResource)
};
requestWithoutMatchResource = request.slice(matchResourceMatch[0].length); // 去除匹配到的资源部分
}
scheme = getScheme(requestWithoutMatchResource);
if (!scheme && !contextScheme) {
const firstChar = requestWithoutMatchResource.charCodeAt(0);
const secondChar = requestWithoutMatchResource.charCodeAt(1);
noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!"
const rawElements = requestWithoutMatchResource.slice(
noPreAutoLoaders || noPrePostAutoLoaders ? 2 : noAutoLoaders ? 1 : 0
).split(/!+/);
unresolvedResource = rawElements.pop(); // 获取未解析的资源
elements = rawElements.map(el => {
const { path, query } = cachedParseResourceWithoutFragment(el); // 解析 loader 的路径和查询字符串
return {
loader: path,
options: query ? query.slice(1) : undefined
};
});
scheme = getScheme(unresolvedResource);
} else {
unresolvedResource = requestWithoutMatchResource;
elements = EMPTY_ELEMENTS;
}
} else {
unresolvedResource = request;
elements = EMPTY_ELEMENTS;
}
const resolveContext = {
fileDependencies,
missingDependencies,
contextDependencies
};
let resourceData;
let loaders;
const continueCallback = needCalls(2, err => {
if (err) return callback(err);
try {
// 处理 loader 的配置
for (const item of loaders) {
if (typeof item.options === "string" && item.options[0] === "?") {
const ident = item.options.slice(1);
if (ident === "[[missing ident]]") {
throw new Error(
"No ident is provided by referenced loader. When using a function for Rule.use in config you need to provide an 'ident' property for referenced loader options."
);
}
item.options = this.ruleSet.references.get(ident);
if (item.options === undefined) {
throw new Error("Invalid ident is provided by referenced loader");
}
item.ident = ident;
}
}
} catch (identErr) {
return callback(identErr);
}
if (!resourceData) {
return callback(null, dependencies[0].createIgnoredModule(context)); // 如果没有资源数据,返回忽略模块
}
const userRequest = (matchResourceData !== undefined ? `${matchResourceData.resource}!=!` : "") +
stringifyLoadersAndResource(loaders, resourceData.resource);
const settings = {};
const useLoadersPost = [];
const useLoaders = [];
const useLoadersPre = [];
let resource;
let match;
if (matchResourceData && typeof (resource = matchResourceData.resource) === "string" && (match = /\.webpack\[([^\]]+)\]$/.exec(resource))) {
settings.type = match[1];
matchResourceData.resource = matchResourceData.resource.slice(0, -settings.type.length - 10);
} else {
settings.type = JAVASCRIPT_MODULE_TYPE_AUTO;
const resourceDataForRules = matchResourceData || resourceData;
const result = this.ruleSet.exec({
resource: resourceDataForRules.path,
realResource: resourceData.path,
resourceQuery: resourceDataForRules.query,
resourceFragment: resourceDataForRules.fragment,
scheme,
assertions,
mimetype: matchResourceData ? "" : resourceData.data.mimetype || "",
dependency: dependencyType,
descriptionData: matchResourceData ? undefined : resourceData.data.descriptionFileData,
issuer: contextInfo.issuer,
compiler: contextInfo.compiler,
issuerLayer: contextInfo.issuerLayer || ""
});
for (const r of result) {
if (r.type === "use") {
if (!noAutoLoaders && !noPrePostAutoLoaders) {
useLoaders.push(r.value);
}
} else if (r.type === "use-post") {
if (!noPrePostAutoLoaders) {
useLoadersPost.push(r.value);
}
} else if (r.type === "use-pre") {
if (!noPreAutoLoaders && !noPrePostAutoLoaders) {
useLoadersPre.push(r.value);
}
} else if (typeof r.value === "object" && r.value !== null && typeof settings[r.type] === "object" && settings[r.type] !== null) {
settings[r.type] = cachedCleverMerge(settings[r.type], r.value);
} else {
settings[r.type] = r.value;
}
}
}
let postLoaders;
let normalLoaders;
let preLoaders;
const continueCallback = needCalls(3, err => {
if (err) return callback(err);
const allLoaders = postLoaders;
if (matchResourceData === undefined) {
loaders.forEach(loader => allLoaders.push(loader));
normalLoaders.forEach(loader => allLoaders.push(loader));
} else {
normalLoaders.forEach(loader => allLoaders.push(loader));
loaders.forEach(loader => allLoaders.push(loader));
}
preLoaders.forEach(loader => allLoaders.push(loader));
const type = settings.type;
const resolveOptions = settings.resolve;
const layer = settings.layer;
if (layer !== undefined && !layers) {
return callback(new Error("'Rule.layer' is only allowed when 'experiments.layers' is enabled"));
}
try {
Object.assign(data.createData, {
layer: layer === undefined ? contextInfo.issuerLayer || null : layer,
request: stringifyLoadersAndResource(allLoaders, resourceData.resource),
userRequest,
rawRequest: request,
loaders: allLoaders,
resource: resourceData.resource,
context: resourceData.context || getContext(resourceData.resource),
matchResource: matchResourceData ? matchResourceData.resource : undefined,
resourceResolveData: resourceData.data,
settings,
type,
parser: this.getParser(type, settings.parser),
parserOptions: settings.parser,
generator: this.getGenerator(type, settings.generator),
generatorOptions: settings.generator,
resolveOptions
});
} catch (createDataErr) {
return callback(createDataErr);
}
callback();
});
this.resolveRequestArray(contextInfo, this.context, useLoadersPost, loaderResolver, resolveContext, (err, result) => {
postLoaders = result;
continueCallback(err);
});
this.resolveRequestArray(contextInfo, this.context, useLoaders, loaderResolver, resolveContext, (err, result) => {
normalLoaders = result;
continueCallback(err);
});
this.resolveRequestArray(contextInfo, this.context, useLoadersPre, loaderResolver, resolveContext, (err, result) => {
preLoaders = result;
continueCallback(err);
});
});
this.resolveRequestArray(contextInfo, contextScheme ? this.context : context, elements, loaderResolver, resolveContext, (err, result) => {
if (err) return continueCallback(err);
loaders = result;
continueCallback();
});
const defaultResolve = context => {
if (/^($|\?)/.test(unresolvedResource)) {
resourceData = {
resource: unresolvedResource,
data: {},
...cacheParseResource(unresolvedResource)
};
continueCallback();
} else {
const normalResolver = this.getResolver("normal", dependencyType ? cachedSetProperty(resolveOptions || EMPTY_RESOLVE_OPTIONS, "dependencyType", dependencyType) : resolveOptions);
this.resolveResource(
contextInfo,
context,
unresolvedResource,
normalResolver,
resolveContext,
(err, _resolvedResource, resolvedResourceResolveData) => {
if (err) return continueCallback(err);
if (_resolvedResource !== false) {
const resolvedResource = _resolvedResource;
resourceData = {
resource: resolvedResource,
data: resolvedResourceResolveData,
...cacheParseResource(resolvedResource)
};
}
continueCallback();
}
);
}
};
if (scheme) {
resourceData = {
resource: unresolvedResource,
data: {},
path: undefined,
query: undefined,
fragment: undefined,
context: undefined
};
this.hooks.resolveForScheme.for(scheme).callAsync(resourceData, data, err => {
if (err) return continueCallback(err);
continueCallback();
});
} else if (contextScheme) {
resourceData = {
resource: unresolvedResource,
data: {},
path: undefined,
query: undefined,
fragment: undefined,
context: undefined
};
this.hooks.resolveInScheme.for(contextScheme).callAsync(resourceData, data, (err, handled) => {
if (err) return continueCallback(err);
if (!handled) return defaultResolve(this.context);
continueCallback();
});
} else defaultResolve(context);
}
);
}