# 插件间通信

在使用许多专用插件时,可能需要无关插件能够在构建期间交换信息。Rollup 通过几种机制使这成为可能。

# 自定义解析器选项

假设你有一个插件,应根据另一个插件生成的导入方式将导入解析为不同的 ID。实现此目的的一种方法是将导入重写为使用特殊代理 ID,例如,在 CommonJS 文件中通过 require("foo") 进行转换的导入可以成为具有特殊 ID import "foo?require=true" 的常规导入,以便解析器插件知道这一点。

然而,这里的问题是,这个代理 ID 可能会导致意外的副作用,因为它实际上并不对应于一个文件。此外,如果 ID 是由插件 A 创建的,并且解析发生在插件 B 中,则会在这些插件之间创建一个依赖关系,以便 A 无法在没有 B 的情况下使用。

自定义解析器选项提供了一种解决方案,允许在通过 this resolve 手动解析模块时为插件传递附加选项。这发生在不更改 ID 的情况下,因此如果目标插件不存在,则不会影响其他插件正确解析模块的能力。

function requestingPlugin() {
	return {
		name: 'requesting',
		async buildStart() {
			const resolution = await this.resolve('foo', undefined, {
				custom: { resolving: { specialResolution: true } }
			});
			console.log(resolution.id); // "special"
		}
	};
}

function resolvingPlugin() {
	return {
		name: 'resolving',
		resolveId(id, importer, { custom }) {
			if (custom.resolving?.specialResolution) {
				return 'special';
			}
			return null;
		}
	};
}

请注意,自定义选项应使用与解析插件的插件名称相对应的属性添加。解析插件有责任指定它所支持的选项。

# 自定义模块元数据

插件可以使用 resolveId、load 和 transform 钩子通过自己和其他插件为模块添加自定义元数据,并通过 this.getModuleInfo、this.load 和 moduleParsed 钩子访问该元数据。此元数据应始终是可 JSON.stringify 的,并将在缓存中持久化,例如在监视模式下。

function annotatingPlugin() {
	return {
		name: 'annotating',
		transform(code, id) {
			if (thisModuleIsSpecial(code, id)) {
				return { meta: { annotating: { special: true } } };
			}
		}
	};
}

function readingPlugin() {
	let parentApi;
	return {
		name: 'reading',
		buildEnd() {
			const specialModules = Array.from(this.getModuleIds()).filter(
				id => this.getModuleInfo(id).meta.annotating?.special
			);
			// 用 specialModules 做一些工作
		}
	};
}

请注意,添加或修改数据的插件应使用与插件名称相对应的属性,例如在此示例中为 annotating。另一方面,任何插件都可以通过 this.getModuleInfo 读取其他插件的所有元数据。

如果多个插件添加元数据或在不同的钩子中添加元数据,则这些 meta 对象将被浅合并。这意味着如果插件 first 在 resolveId 钩子中添加了 {meta: {first: {resolved: "first"}}},在 load 钩子中添加了 {meta: {first: {loaded: "first"}}},而插件 second 在 transform 钩子中添加了 {meta: {second: {transformed: "second"}}},则生成的 meta 对象将是 {first: {loaded: "first"}, second: {transformed: "second"}}。这里 resolveId 钩子的结果将被 load 钩子的结果覆盖,因为该插件都将它们存储在其 first 顶级属性下。另一方面,另一个插件的 transform 数据将放在其旁边。

模块的 meta 对象在 Rollup 开始加载模块时创建,并在模块的每个生命周期钩子中更新。如果你存储对此对象的引用,则还可以手动更新它。要访问尚未加载的模块的元数据对象,可以通过 this.load 触发其创建和加载该模块:

function plugin() {
	return {
		name: 'test',
		buildStart() {
			// 触发加载模块。
			// 我们也可以在这里传递一个初始的“meta”对象,
			// 但如果模块已经通过其他方式加载,则会被忽略
			this.load({ id: 'my-id' });
			// 现在模块信息可用,
			// 我们不需要等待 this.load
			const meta = this.getModuleInfo('my-id').meta;
			// 现在我们也可以手动修改 meta
			meta.test = { some: 'data' };
		}
	};
}

# 插件间的直接通信

对于任何其他类型的插件间通信,我们推荐以下模式。请注意,api 永远不会与任何即将推出的插件钩子冲突。

function parentPlugin() {
	return {
		name: 'parent',
		api: {
			//...暴露给其他插件的方法和属性
			doSomething(...args) {
				// 做一些有趣的事情
			}
		}
		// ...插件钩子
	};
}

function dependentPlugin() {
	let parentApi;
	return {
		name: 'dependent',
		buildStart({ plugins }) {
			const parentName = 'parent';
			const parentPlugin = plugins.find(
				plugin => plugin.name === parentName
			);
			if (!parentPlugin) {
				// 或者如果是可选的,可以静默处理
				throw new Error(
					`此插件依赖于 “${parentName}” 插件。`
				);
			}
			// 现在你可以在后续钩子中访问 API 方法
			parentApi = parentPlugin.api;
		},
		transform(code, id) {
			if (thereIsAReasonToDoSomething(id)) {
				parentApi.doSomething(id);
			}
		}
	};
}
Last Updated: 6/14/2023, 8:56:23 AM