# this.load

Type:
Load
type Load = (options: {
	id: string;
	resolveDependencies?: boolean;
	assertions?: Record<string, string> | null;
	meta?: CustomPluginOptions | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	syntheticNamedExports?: boolean | string | null;
}) => Promise<ModuleInfo>;

加载并解析与给定 ID 对应的模块,并在提供的情况下附加附加的元信息到模块。这将触发与其他模块导入该模块时相同的 load、transform 和 moduleParsed 钩子。

这使你可以在决定如何在 resolveId 钩子中解析它们之前检查模块的最终内容,并解析为代理模块。如果模块稍后成为图的一部分,则使用此上下文函数没有额外的开销,因为模块不会再次解析。该签名允许你直接将 this.resolve 的返回值传递给此函数,只要它既不是 null 也不是外部的。

返回的 Promise 将在模块完全转换和解析之后但在解析任何导入之前解析。这意味着生成的 ModuleInfo 将具有空的 importedIds、dynamicallyImportedIds、importedIdResolutions 和 dynamicallyImportedIdResolutions。这有助于避免在 resolveId 钩子中等待 this.load 时出现死锁情况。如果你对 importedIds 和 dynamicallyImportedIds 感兴趣,可以实现 moduleParsed 钩子或传递 resolveDependencies 标志,这将使 this.load 返回的 Promise 等待所有依赖项 ID 解析。

请注意,关于 assertions、meta、moduleSideEffects 和 syntheticNamedExports 选项,与 resolveId 钩子相同的限制适用:仅当模块尚未加载时,它们的值才会生效。因此,非常重要的是首先使用 this.resolve 查找任何插件是否想要在其 resolveId 钩子中设置这些选项的特殊值,并在适当时将这些选项传递给 this.load。下面的示例展示了如何处理包含特殊代码注释的模块以添加代理模块。请注意重新导出默认导出的特殊处理:

export default function addProxyPlugin() {
	return {
		async resolveId(source, importer, options) {
			if (importer?.endsWith('?proxy')) {
				// 不代理使用代理的 ID
				return null;
			}
			// 确保将任何 resolveId 选项传递给 this.resolve
			// 以获取模块 ID
			const resolution = await this.resolve(source, importer, {
				skipSelf: true,
				...options
			});
			// 只能预加载现有且非外部的 ID
			if (resolution && !resolution.external) {
				// 将整个解析信息传递下去
				const moduleInfo = await this.load(resolution);
				if (moduleInfo.code.includes('/* use proxy */')) {
					return `${resolution.id}?proxy`;
				}
			}
			// 由于我们已经完全解析了模块
			// 因此没有理由再次解析它
			return resolution;
		},
		load(id) {
			if (id.endsWith('?proxy')) {
				const importee = id.slice(0, -'?proxy'.length);
				// 请注意
				// 命名空间重新导出不会重新导出默认导出
				let code = `console.log('proxy for ${importee}'); export * from ${JSON.stringify(
					importee
				)};`;
				// 我们知道在解析代理时
				// importee 已经完全加载和解析
				// 因此我们可以依赖 hasDefaultExport
				if (this.getModuleInfo(importee).hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(importee)};`;
				}
				return code;
			}
			return null;
		}
	};
}

如果模块已经被加载,this.load 将等待解析完成并返回其模块信息。如果模块尚未被其他模块导入,则不会自动触发加载此模块导入的其他模块。相反,只有在此模块至少被导入一次后,静态和动态依赖项才会被加载一次。

虽然在 resolveId 钩子中使用 this.load 是安全的,但在 load 或 transform 钩子中等待它时应非常小心。如果模块图中存在循环依赖关系,这很容易导致死锁,因此任何插件都需要手动注意避免在处于循环中的任何模块的 load 或 transform 中等待 this.load。

这里是另一个更详细的示例,其中我们通过 resolveDependencies 选项和对 this.load 的重复调用来扫描整个依赖子图。我们使用已处理模块 ID 的 Set 来处理循环依赖关系。插件的目标是向每个动态导入的块添加日志,该日志仅列出块中的所有模块。虽然这只是一个玩具示例,但该技术可以用于例如为子图中导入的所有 CSS 创建单个样式标记。

// 前导的 \0 指示其他插件不要尝试解析、加载
// 或转换我们的代理模块
const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:';

export default function dynamicChunkLogsPlugin() {
	return {
		name: 'dynamic-chunk-logs',
		async resolveDynamicImport(specifier, importer) {
			// 忽略非静态目标
			if (!(typeof specifier === 'string')) return;
			// 获取导入目标的 id 和初始元信息
			const resolved = await this.resolve(specifier, importer);
			// 忽略外部目标。
			// 显式外部具有 "external" 属性,而未解析的导入为 "null"。
			if (resolved && !resolved.external) {
				// 我们在这里触发加载模块,
				// 而不等待它,
				// 因为 resolveId 钩子中附加的元信息,
				// 可能包含在 "resolved" 中,
				// 像 "commonjs" 这样的插件可能依赖于它,
				// 只有在第一次加载模块时才会附加到模块上。
				// 这确保了在稍后使用 "this.load" 再次在 load 钩子中使用仅模块 id 时,
				// 不会丢失此元信息。
				this.load(resolved);
				return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`;
			}
		},
		async load(id) {
			// 忽略所有文件,除了我们的动态导入代理
			if (!id.startsWith('\0dynamic-import:')) return null;
			const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length);
			// 为了允许并行加载模块,同时保持复杂度低,
			// 我们不直接等待每个 "this.load" 调用,
			// 而是将它们的 Promise 放入一个数组中,
			// 在 async for 循环中通过 await 它们。
			const moduleInfoPromises = [
				this.load({ id: actualId, resolveDependencies: true })
			];
			// 我们在这里跟踪每个已加载的依赖项,
			// 以便我们不会加载文件两次,
			// 并且在存在循环依赖项时也不会卡住。
			const dependencies = new Set([actualId]);
			// "importedIdResolutions" 跟踪 resolveId 钩子创建的对象。
			// 我们使用这些对象而不是 "importedIds",
			// 以便再次不会丢失重要的元信息。
			for await (const { importedIdResolutions } of moduleInfoPromises) {
				for (const resolved of importedIdResolutions) {
					if (!dependencies.has(resolved.id)) {
						dependencies.add(resolved.id);
						moduleInfoPromises.push(
							this.load({ ...resolved, resolveDependencies: true })
						);
					}
				}
			}
			// 当动态块加载时,我们记录其中的所有模块。
			let code = `console.log([${[...dependencies]
				.map(JSON.stringify)
				.join(', ')}]); export * from ${JSON.stringify(actualId)};`;
			// 命名空间重新导出不会重新导出默认导出,
			// 因此如果存在默认导出,我们需要手动重新导出它
			if (this.getModuleInfo(actualId).hasDefaultExport) {
				code += `export { default } from ${JSON.stringify(actualId)};`;
			}
			return code;
		}
	};
}
Last Updated: 6/14/2023, 8:56:23 AM