Skip to content

插件 API

概览

Rolldown 的插件接口几乎完全兼容 Rollup(详细进度见 此处)。因此,如果以前编写过 Rollup 插件,你已经知道如何编写 Rolldown 插件了!

Rolldown 插件是满足下文 插件接口 的对象。 插件应以包的形式分发。该包导出一个函数,函数接收插件专用选项,并返回这样的对象。

插件可以自定义 Rolldown 的行为,例如在打包前转译代码,或为不可用的内置模块提供垫片。

示例

以下示例展示了一个 Rolldown 插件,它会拦截对 virtual:example 的导入请求,并返回自定义内容。

js
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 按常规方式处理
    },
  };
}
js
import { defineConfig } from 'rolldown';
import examplePlugin from './rolldown-plugin-example.js';

export default defineConfig({
  plugins: [examplePlugin()],
});

钩子过滤器

为保持简单,该示例插件没有使用 钩子过滤器。 为提高性能,建议尽可能使用钩子过滤器。

约定

  • 插件应使用带 rolldown-plugin- 前缀的清晰名称。
  • 在 package.json 的 keywords 字段中包含 rolldown-plugin 关键字。
  • 如有需要,请确保插件输出正确的 source map。
  • 如果插件使用 虚拟模块,请遵循 虚拟模块约定
  • (推荐)应为插件编写测试。
  • (推荐)应使用英文编写插件文档。

虚拟模块约定

虚拟模块是一种实用机制,允许使用普通 ESM 导入语法向源文件传递构建时信息或辅助函数。虚拟模块并不存在于文件系统中,而是由插件解析并提供,如 上面的示例 所示。

注册这类插件后,便可通过面向用户的 ID 在 JavaScript 中导入虚拟模块:

js
import msg from 'virtual:example';

console.log(msg);

按照约定,Rolldown 虚拟模块面向用户的路径以 virtual: 为前缀。应尽可能使用插件名作为命名空间,避免与生态中的其他插件冲突。例如,rolldown-plugin-posts 可以要求用户导入 virtual:postsvirtual:posts/helpers 虚拟模块来获取构建时信息。在内部,使用虚拟模块的插件解析 ID 时应为模块 ID 添加 \0 前缀,这是来自 Rollup 生态的约定。这样可以防止其他插件尝试处理该 ID(例如进行 Node.js 解析),source map 等核心功能也可以据此区分虚拟模块和普通文件。

请注意,直接派生自真实文件的模块无需遵循此约定,例如单文件组件(.vue.svelte SFC)中的脚本模块。SFC 在处理时通常会生成一组子模块,但其中的代码可以映射回文件系统。为这些子模块使用 \0 会导致 source map 无法正常工作。

插件接口

Plugin 接口包含必需的 name 属性,以及多个可选属性和钩子。

钩子是定义在插件上的方法,用于与构建流程交互。它们会在构建的不同阶段被调用,可以影响构建的运行方式、提供构建信息,或在构建完成后修改结果。钩子分为以下类型:

  • async:钩子也可以返回解析为相同类型值的 Promise;否则会标记为 sync
  • first:如果多个插件实现了该钩子,会依次运行,直到某个钩子返回非 nullundefined 的值。
  • sequential:如果多个插件实现了该钩子,会按指定的插件顺序全部运行。如果钩子是 async,后续同类钩子会等待当前钩子完成。
  • parallel:如果多个插件实现了该钩子,会按指定的插件顺序全部启动。如果钩子是 async,后续同类钩子会并行运行,不会等待当前钩子。

钩子也可以是带有 handler 属性的对象,而不是方法。此时 handler 属性才是真正的钩子方法。这样便可提供额外的可选属性来控制钩子行为。更多信息请参阅 ObjectHook 类型。

钩子分为两类:构建钩子输出生成钩子

构建钩子

构建钩子在构建阶段运行,主要负责在 Rolldown 处理输入文件前定位、提供和转换这些文件。

构建阶段的第一个钩子是 options,最后一个始终是 buildEnd。如果发生构建错误,随后还会调用 closeBundle

watchchangewatchChangeclosewatchercloseWatcheroptionsoptionsoutputoptionsoutputOptionsoptions->outputoptionsbuildstartbuildStartoutputoptions->buildstartresolveidresolveIdbuildstart->resolveideach entryloadloadresolveid->loadnon-externalbuildendbuildEndresolveid->buildendexternaltransformtransformload->transforminternaltransforminternalTransformtransform->internaltransformmoduleparsedmoduleParsedmoduleparsed->resolveideach importresolvedynamicimportresolveDynamicImportmoduleparsed->resolvedynamicimporteach import()moduleparsed->buildendno importsinternaltransform->moduleparsedresolvedynamicimport->resolveidunresolvedresolvedynamicimport->loadnon-externalresolvedynamicimport->buildendexternal
Legend
sequential
parallel
first
internal
sync
async
watchchangewatchChangeclosewatchercloseWatcheroptionsoptionsoutputoptionsoutputOptionsoptions->outputoptionsbuildstartbuildStartoutputoptions->buildstartresolveidresolveIdbuildstart->resolveideach entryloadloadresolveid->loadnon-externalbuildendbuildEndresolveid->buildendexternaltransformtransformload->transforminternaltransforminternalTransformtransform->internaltransformmoduleparsedmoduleParsedmoduleparsed->resolveideach importresolvedynamicimportresolveDynamicImportmoduleparsed->resolvedynamicimporteach import()moduleparsed->buildendno importsinternaltransform->moduleparsedresolvedynamicimport->resolveidunresolvedresolvedynamicimport->loadnon-externalresolvedynamicimport->buildendexternal
Legend
sequential
parallel
first
internal
sync
async

请注意,上图中的 internalTransform 不是插件钩子,而是 Rolldown 将非 JS 代码转换为 JS 的步骤。

此外,在监听模式下,watchChange 钩子可能随时触发,用于通知当前运行生成输出后将启动新一轮构建。监听器关闭时还会触发 closeWatcher 钩子。

不支持的钩子

以下构建钩子受 Rollup 支持,但 Rolldown 尚不支持:

  • shouldTransformCachedModule (#4389)

输出生成钩子

输出生成钩子可以提供已生成打包产物的信息,并在构建完成后修改结果。只使用输出生成钩子的插件也可以通过输出选项传入,从而仅针对特定输出运行。

输出生成阶段的第一个钩子是 renderStart。如果通过 bundle.generate(...) 成功生成输出,最后一个钩子是 generateBundle;如果通过 bundle.write(...) 成功生成输出,则是 writeBundle;如果输出生成期间发生错误,则是 renderError

此外,closeBundle 可以作为最后一个钩子被调用,但用户需要手动调用 bundle.close() 才能触发。CLI 始终会确保执行此操作。

cluster_generatechunksrenderstartrenderStartbeforeimportmetarenderstart->beforeimportmetaeach chunkresolvefileurlresolveFileUrlresolvefileurl->beforeimportmetabannerbannerafteraddonsbanner->afteraddonsfooterfooterfooter->afteraddonsintrointrointro->afteraddonsoutrooutrooutro->afteraddonsrenderchunkrenderChunkminifyminifyrenderchunk->minifypostbannerpostBannerminify->postbannerpostfooterpostFooterminify->postfooteraugmentchunkhashaugmentChunkHashpostbanner->augmentchunkhashpostfooter->augmentchunkhashaugmentchunkhash->renderchunknext chunkgeneratebundlegenerateBundleaugmentchunkhash->generatebundlewritebundlewriteBundlegeneratebundle->writebundleclosebundlecloseBundlewritebundle->closebundlerendererrorrenderErrorrendererror->closebundlebeforeimportmeta->resolvefileurleach import.meta.ROLLDOWN_FILE_URL_*beforeaddonsbeforeimportmeta->beforeaddonsbeforeaddons->bannerbeforeaddons->footerbeforeaddons->introbeforeaddons->outroafteraddons->renderchunkeach chunkafteraddons->beforeimportmetanext chunk
Legend
sequential
parallel
first
internal
sync
async
cluster_generatechunksrenderstartrenderStartbeforeimportmetarenderstart->beforeimportmetaeach chunkresolvefileurlresolveFileUrlresolvefileurl->beforeimportmetabannerbannerafteraddonsbanner->afteraddonsfooterfooterfooter->afteraddonsintrointrointro->afteraddonsoutrooutrooutro->afteraddonsrenderchunkrenderChunkminifyminifyrenderchunk->minifypostbannerpostBannerminify->postbannerpostfooterpostFooterminify->postfooteraugmentchunkhashaugmentChunkHashpostbanner->augmentchunkhashpostfooter->augmentchunkhashaugmentchunkhash->renderchunknext chunkgeneratebundlegenerateBundleaugmentchunkhash->generatebundlewritebundlewriteBundlegeneratebundle->writebundleclosebundlecloseBundlewritebundle->closebundlerendererrorrenderErrorrendererror->closebundlebeforeimportmeta->resolvefileurleach import.meta.ROLLDOWN_FILE_URL_*beforeaddonsbeforeimportmeta->beforeaddonsbeforeaddons->bannerbeforeaddons->footerbeforeaddons->introbeforeaddons->outroafteraddons->renderchunkeach chunkafteraddons->beforeimportmetanext chunk
Legend
sequential
parallel
first
internal
sync
async

请注意,上图中的 minify 不是插件钩子,而是 Rolldown 运行压缩器的步骤。postBannerpostFooter 也不是插件钩子,它们是输出选项;与 bannerfooter 不同,它们没有对应的钩子。

不支持的钩子

以下输出生成钩子受 Rollup 支持,但 Rolldown 尚不支持:

  • resolveImportMeta (#1010)
  • renderDynamicImport (#4532)

插件上下文

在大多数钩子中,可以通过 this 访问多种工具函数和信息。更多内容请参阅 PluginContext 类型。

支持 TypeScript 和 JSX

为了获得最佳性能,Rolldown 会在调用 transform 钩子后才运行内部转换,把 TypeScript 和 JSX 转换为 JavaScript。这意味着使用 transform 钩子的插件需要支持 TypeScript 和 JSX。基本上有两种实现方式。

处理 TypeScript 和 JSX 语法

this.parse 传递 lang 选项,即可解析 TypeScript 和 JSX,让插件轻松处理这两种语法。

预先转换 TypeScript 和 JSX

如果无法处理 TypeScript 和 JSX AST,仍可使用 rolldown/utils 导出的 transform 函数先将它们转换为 JavaScript。请注意,这会产生额外开销。

与 Rollup 的主要区别

虽然 Rolldown 的插件接口大体兼容 Rollup,但仍需注意一些重要的行为差异:

输出生成处理方式

在 Rollup 中,所有输出都在同一流程中一起生成;而 Rolldown 会分别处理每个输出。也就是说,如果存在多份输出配置,Rolldown 会独立处理每个输出。这可能影响某些插件的行为,尤其是在整个构建过程中维护状态的插件。

具体区别如下:

  • 在 Rolldown 中,outputOptions 钩子在构建钩子之前调用,而 Rollup 在构建钩子之后调用。
  • 每个输出都会分别调用构建钩子,而 Rollup 只为所有输出调用一次。
  • 只有至少调用过一次 generate()write() 时,Rolldown 才会调用 closeBundle 钩子;Rollup 则无论是否调用过 generate()write() 都会调用。

监听模式中的钩子行为

在 Rollup 中,监听模式每次重新构建都会调用 options 钩子。在 Rolldown 中,options 钩子只在创建监听器时调用一次,后续重新构建不会再次调用。

顺序执行钩子

在 Rollup 中,writeBundle 等部分钩子默认是“并行”的,也就是会跨多个插件并发运行。如果需要钩子依次运行,插件必须显式设置 sequential: true

在 Rolldown 中,writeBundle 钩子默认已经顺序执行,因此插件无需为该钩子指定 sequential: true

Was this page helpful?