插件 API
概述
Rolldown 的插件接口几乎与 Rollup 的完全兼容(详细跟踪 见此),所以如果你以前写过 Rollup 插件,你已经知道如何编写 Rolldown 插件了!
Rolldown 插件是一个满足下面所述 插件接口 的对象。 插件应当以包的形式发布,并导出一个可用插件特定选项调用的函数,该函数返回这样一个对象。
插件允许你自定义 Rolldown 的行为,例如,在打包之前转译代码,或者为一个不可用的内置模块提供 shim。
示例
下面的示例展示了一个 Rolldown 插件,它会拦截对 virtual:example 的导入请求,并为其返回自定义内容。
const id = 'virtual:example';
const resolvedId = '\0' + id;
export default function examplePlugin() {
return {
name: 'example-plugin', // 这个名称会显示在日志和错误中
resolveId(source) {
if (source === id) {
// 这表示告诉 Rolldown,这个导入应解析为名为 `\0virtual:example` 的模块
return resolvedId;
}
return null; // 其他 id 应按常规方式处理
},
load(id) {
if (id === resolvedId) {
// `\0virtual:example` 的源代码
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()],
});钩子过滤器
为了简单起见,这个示例插件没有使用 钩子过滤器。 为了提升性能,建议在可能时使用它们。
约定
- 插件应具有清晰的名称,并带有
rolldown-plugin-前缀。 - 在 package.json 的
keywords字段中包含rolldown-plugin关键字。 - 请确保插件在适当情况下输出正确的源映射。
- 如果你的插件使用了 虚拟模块,请遵循 虚拟模块约定。
- (推荐)插件应进行测试。
- (推荐)插件应使用英文进行文档编写。
虚拟模块约定
虚拟模块是一种有用的方案,它允许你通过普通的 ESM import 语法,将构建时信息或辅助函数传递给源文件。虚拟模块是文件系统中不存在的模块,而是由插件解析并提供的,如 上面的示例 所示。
一旦注册了这样的插件,就可以通过其面向用户的 id 在 JavaScript 中导入该虚拟模块:
import msg from 'virtual:example';
console.log(msg);按照约定,Rolldown 中的虚拟模块在面向用户的路径前缀中使用 virtual:。如果可能,应使用插件名称作为命名空间,以避免与生态系统中的其他插件发生冲突。例如,rolldown-plugin-posts 可以让用户导入 virtual:posts 或 virtual:posts/helpers 虚拟模块,以获取构建时信息。在内部,使用虚拟模块的插件在解析 id 时应当为模块 ID 添加 \0 前缀,这是来自 Rollup 生态系统的一种约定。这可以防止其他插件尝试处理该 id(例如 node 解析),并且像 sourcemaps 这样的核心功能可以利用这一信息区分虚拟模块和普通文件。
请注意,直接从真实文件派生出来的模块,例如单文件组件(如 .vue 或 .svelte SFC)中的脚本模块,不需要遵循这一约定。SFC 在处理时通常会生成一组子模块,但这些代码可以映射回文件系统。对这些子模块使用 \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 那样有对应的钩子。
插件上下文
大多数钩子中都可以通过 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。
Sourcemap 验证
Rollup 不会根据插件 sourcemap 自身的 sources 和 names 对其进行检查。指向缺失源的映射会被丢弃。指向缺失名称的映射会被保留,但不包含该名称。Rolldown 在将映射转换为内部表示时会检查每个索引。因此,Rollup 可以接受的无效映射可能会导致此处构建失败。例如:
Failed to convert json sourcemap to struct
Reference to non-existing source at position 1