# 插件间通信
在使用许多专用插件时,可能需要无关插件能够在构建期间交换信息。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);
}
}
};
}
← 合成命名导出