插件 API
概述
Rolldown 的插件接口几乎与 Rollup 的完全兼容(详细跟踪 见此),所以如果你以前写过 Rollup 插件,你已经知道如何编写 Rolldown 插件了!
Rolldown 插件是一个满足下面所述 插件接口 的对象。 插件应当以包的形式发布,并导出一个可用插件特定选项调用的函数,该函数返回这样一个对象。
插件允许你自定义 Rolldown 的行为,例如,在打包之前转译代码,或者为一个不可用的内置模块提供 shim。
示例
下面的示例展示了一个 Rolldown 插件,它会拦截对 example-virtual-module 的导入请求,并为其返回自定义内容。
const id = 'example-virtual-module';
const resolvedId = '\0' + id;
export default function examplePlugin() {
return {
name: 'example-plugin', // 这个名称会显示在日志和错误中
resolveId(source) {
if (source === id) {
// 这会向 Rolldown 表示该导入应解析为名为 `\0example-virtual-module` 的模块
return resolvedId;
}
return null; // 其他 id 应按常规方式处理
},
load(id) {
if (id === resolvedId) {
// `\0example-virtual-module` 的源代码
return `export default 'Hello from ${id}';`;
}
return null; // 其他 id 应按常规方式处理
},
};
}import { defineConfig } from 'rolldown';
import examplePlugin from './rolldown-plugin-example.js';
export default defineConfig({
plugins: [examplePlugin()],
});虚拟模块
此插件实现了一种通常称为“虚拟模块”的模式。 虚拟模块是文件系统中不存在的模块,而是由插件解析并提供。 在上面的示例中,example-virtual-module 从未从磁盘读取,因为插件在 resolveId 中拦截了导入,并在 load 中提供了该模块的源代码。 这种模式适用于注入辅助函数。
Hook Filters
为了简单起见,这个示例插件没有使用 Hook Filters。 为了提升性能,建议在可能时使用它们。
约定
- 插件应具有清晰的名称,并带有
rolldown-plugin-前缀。 - 在 package.json 的
keywords字段中包含rolldown-plugin关键字。 - 确保你的插件在适当情况下输出正确的源映射。
- 如果你的插件使用了 "virtual modules",请在模块 ID 前加上
\0前缀。这可以防止其他插件尝试处理它。 - (推荐)插件应进行测试。
- (推荐)插件应使用英文文档。
插件接口
Plugin 接口具有一个必需的 name 属性以及多个可选属性和钩子。
钩子是定义在插件上的方法,可用于与构建过程交互。它们会在构建的各个阶段被调用。钩子可以影响构建的运行方式,提供构建信息,或者在构建完成后修改构建结果。钩子有不同类型:
async:该钩子也可以返回一个 Promise,解析为同类型的值;否则,该钩子标记为sync。first:如果有多个插件实现了此钩子,这些钩子会按顺序运行,直到某个钩子返回非null或undefined的值。sequential:如果有多个插件实现了此钩子,它们都会按照指定的插件顺序运行。如果某个钩子是async,后续同类钩子会等待当前钩子解析完成。parallel:如果有多个插件实现了此钩子,它们都会按照指定的插件顺序运行。如果某个钩子是async,后续同类钩子会并行运行,不会等待当前钩子。
钩子除了可以是方法,也可以是带有 handler 属性的对象。在这种情况下,handler 属性才是实际的钩子方法。这使你可以提供额外的可选属性来控制钩子的行为。更多信息请参见 ObjectHook 类型。
构建钩子
构建钩子在构建阶段运行。它们主要负责在输入文件被 Rolldown 处理之前定位、提供和转换输入文件。
构建阶段的第一个钩子是 options,最后一个总是 buildEnd。如果发生构建错误,之后会调用 closeBundle。
请注意,上图中的 internalTransform 不是插件钩子,它是 Rolldown 将非 JS 代码转换为 JS 的步骤。
此外,在 watch 模式下,watchChange 钩子可以在任何时候被触发,以通知在当前运行生成输出后将触发一次新的运行。同时,当 watcher 关闭时,closeWatcher 钩子将被触发。
输出生成钩子
输出生成钩子可以提供有关已生成 bundle 的信息,并在完成后修改构建结果。仅使用输出生成钩子的插件也可以通过输出选项传入,因此只会对某些输出运行。
输出生成阶段的第一个钩子是 renderStart,最后一个要么是 generateBundle,前提是输出已通过 bundle.generate(...) 成功生成;要么是 writeBundle,前提是输出已通过 bundle.write(...) 成功生成;或者是在输出生成期间任何时候发生错误时的 renderError。
此外,closeBundle 也可以作为最后一个钩子被调用,但这需要用户手动调用 bundle.close() 来触发。CLI 会始终确保这一点。
请注意,上图中的 minify 不是插件钩子,它是 Rolldown 运行压缩器的步骤。另请注意,postBanner 和 postFooter 不是插件钩子,这些是输出选项,并且不像 banner 和 footer 那样有对应的钩子。
不支持的钩子
Rollup 支持但 Rolldown 不支持的输出生成钩子如下:
插件上下文
大多数钩子中都可以通过 this 访问一些实用函数和信息片段。有关更多信息,请参阅 PluginContext 类型。
支持 TypeScript 和 JSX
为了实现最佳性能,Rolldown 会在调用 transform 钩子后运行内部转换,将 TypeScript 和 JSX 转换为 JavaScript。这意味着使用 transform 钩子的插件需要支持 TypeScript 和 JSX。基本上,实现这一点有两种方式。
处理 TypeScript 和 JSX 语法
this.parse 支持通过传递 lang 选项来解析 TypeScript 和 JSX。这应当能够让插件轻松处理 TypeScript 和 JSX。
预先转换 TypeScript 和 JSX
如果处理 TypeScript 和 JSX AST 不是一个选项,你仍然可以使用从 rolldown/utils 暴露的 transform 函数将它们转换为 JavaScript。请注意,这会带来额外开销。
与 Rollup 的显著差异
尽管 Rolldown 的插件接口与 Rollup 的大体兼容,但仍有一些重要的行为差异需要注意:
输出生成处理
在 Rollup 中,所有输出会在单个进程中一起生成。然而,Rolldown 会分别处理每个输出的生成。这意味着如果你有多个输出配置,Rolldown 将独立处理每个输出,这可能会影响某些插件的行为,尤其是那些在整个构建过程中维护状态的插件。
具体差异如下:
outputOptions钩子在 Rolldown 中会在构建钩子之前调用,而 Rollup 会在构建钩子之后调用- 构建钩子会针对每个输出分别调用,而 Rollup 会对所有输出只调用一次
closeBundle钩子仅在你至少调用过一次generate()或write()时才会调用,而 Rollup 无论你是否调用过generate()或write()都会调用它
监听模式下的钩子行为
在 Rollup 中,options 钩子会在监听模式下的每次重新构建时调用。在 Rolldown 中,options 钩子仅在创建监听器时调用一次,后续的重新构建不会再次调用。
顺序执行钩子
在 Rollup 中,某些钩子如 writeBundle 默认是“并行”的,这意味着它们会在多个插件之间并发运行。如果插件需要按顺序逐个执行这些钩子,就必须显式设置 sequential: true。
在 Rolldown 中,writeBundle 钩子默认已经是顺序执行的,因此插件不需要为此钩子指定 sequential: true。
