Rolldown 插件对象式 Hook 详解:order 排序、filter 过滤与 sequential 兼容

发布时间:2026/9/16 2:03:03
Rolldown 插件对象式 Hook 详解:order 排序、filter 过滤与 sequential 兼容 Rolldown 插件对象式 Hook 详解order 排序、filter 过滤与 sequential 兼容【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown导读在 Rolldown基于 Rust 的高性能 JavaScript/TypeScript 打包器提供 Rollup 兼容 API中插件 Hook 既可以写成纯函数也可以写成携带附加属性的对象形式Object Hook。对象形式为插件作者提供了两个关键能力通过order控制同一 Hook 在多个插件间的执行顺序通过filter让 Hook 仅在匹配条件下被调用由 Rust 侧预先判定省去 JS/Rust 往返开销。本文以 object-hook.md 为核心结合仓库源码深入讲解这两种附加属性Additional Properties的语义、类型约束、使用示例与底层实现帮助你在编写 Rolldown / Rollup 兼容插件时精确掌控 Hook 行为。什么是对象式 Hook函数形式与对象形式Rolldown 的类型系统中一个 Hook 的完整形态被定义为ObjectHookT, O见 plugin/index.tsexport type ObjectHookT, O {} T | ({ handler: T } ObjectHookMeta O);这意味着每个 Hook 都有两种合法写法函数形式简写直接提供一个函数或字符串如banner/footer/intro/outro等 addon Hook例如export default function myPlugin() { return { name: my-plugin, transform(code, id) { return code.replace(foo, bar); }, }; }对象形式Object Hook将真正的回调放进handler字段同时可以附加order、filter等元信息export default function myPlugin() { return { name: my-plugin, transform: { filter: { id: /\.js$/ }, handler(code, id) { return code.replace(foo, bar); }, }, }; }底层适配层在把插件桥接到 Rust 侧binding之前会先通过normalizeHook对两种形式做归一化见 utils/normalize-hook.ts函数/字符串形式被包装为{ handler, options: {}, meta: {} }对象形式则把handler、order从其余附加属性即filter等中分离出来order进入meta其余进入options随后一并传递给bindingifyHook见 bindingify-plugin-hook-meta.ts与各bindingify*绑定函数。order控制多插件间的 Hook 执行顺序类型与语义order属性的类型为type PluginOrder pre | post | null;见 plugin/index.ts。其语义为当多个插件实现了同一个 Hook 时pre表示该插件最先运行post表示最后运行不写或写null则表示保持在用户指定的插件顺序位置上。如果多个插件同时使用了pre或postRolldown 会按照它们在用户配置中出现的先后顺序依次执行。该选项适用于所有插件 Hook。示例优先拦截外部模块解析原文档给出的resolveId示例完整展示了order: pre的用法export default function resolveFirst() { return { name: resolve-first, resolveId: { order: pre, handler(source) { if (source external) { return { id: source, external: true }; } return null; }, }, }; }当source恰好为external时该插件率先将其标记为 external 模块通过返回{ id, external: true }其余情况下返回null将解析权让渡给后续的resolveIdHook 乃至默认解析逻辑resolveId的完整返回值语义见 plugin/index.ts。底层实现order 如何被消费从源码结构看order的传递链路是这样的normalizeHook把对象形式中的order提取到meta.orderbindingifyPluginHookMeta将pre | post | null | undefined映射为 Rust 侧的枚举BindingPluginOrderPre/Post/ 空见 [bindingify-plugin-hook-meta.ts](https://gitcode.com/GitHub_Trending/ro/rolldown/blob/8744801f470e1305a735ad4089c3c3eff4c3a33e/packages/rolldown/src/plugin/bindingify-plugin-hook-meta.ts?utm_sourcegitcode_repo_files#L6-L24非法值会抛出Unknown plugin order错误最终通过BindingPluginOptions上的*Meta字段如resolveIdMeta下发给 Rust 运行时进行调度见 [bindingify-plugin.ts](packages/rolldown/src/plugin/bindingify-plugin.ts#L80-L189。因此order的生效发生在 Rust 核心的 Hook 调度阶段而不是 JS 层手动排序——这也是它性能友好、语义统一的原因。filter让 Hook 只在匹配时被调用类型与适用 Hookfilter属性的类型为HookFilter或TopLevelFilterExpression[]具体取决于 Hook。它仅对resolveId、load、transform三个 Hook 可用这一点在类型层通过HookFilterExtension精确约束见 plugin/index.tstransformfilter支持id、moduleType、codeloadfilter仅支持idresolveIdfilter仅支持id且必须是RegExp不支持字符串原因下文详述renderChunk类型上同样支持基于code的 filter见同一文件HookFilterExtension中对renderChunk的扩展。HookFilter接口定义在 plugin/hook-filter.ts其核心字段为字段类型语义可用 HookidGeneralHookFilter字符串/正则/数组/{include, exclude}对象按模块 id 匹配字符串按 glob 处理正则按路径匹配resolveId、load、transformmoduleTypeModuleType[]或{ include }按模块类型如js、tsx、json匹配仅transformcodeGeneralHookFilter按模块源码内容匹配仅transform其中GeneralHookFilter支持MaybeArrayValue或{ include?, exclude? }两种形态export type GeneralHookFilterValue StringOrRegExp | MaybeArrayValue | { include?: MaybeArrayValue; exclude?: MaybeArrayValue; };示例按 id 与代码内容双重过滤原文档中的transform示例展示了同时使用id与code两个维度进行过滤export default function jsxAdditionalTransform() { return { name: jsxAdditionalTransform, transform: { filter: { id: *.jsx, code: Custom, }, handler(code) { // transform Custom / here }, }, }; }只有当模块 id 匹配 glob*.jsx且源码包含字符串Custom时handler才会被调用。多个 filter 属性之间是“且”的关系——只要其中一个不匹配整个 Hook 就被跳过。include内部是“或”的关系exclude优先级高于include完整匹配规则见 docs/apis/plugin-api/hook-filters.md。filter 的价值把匹配判定下沉到 Rust 侧与在 handler 内部用if提前 return 的传统写法相比filter 的关键差异在于判定发生在 Rust 侧Rolldown 只在 filter 命中时才发起 Rust→JS 的跨语言调用。文档 docs/apis/plugin-api/hook-filters.md 指出这能避免大量无谓的 JS 调用并提升并行化空间。底层实现filter 的绑定与校验bindingify-hook-filter.tspackages/rolldown/src/plugin/bindingify-hook-filter.ts负责把 JS 侧的 filter 描述编译成 Rust 侧可消费的BindingFilterToken[]token 序列包括And/Or/Not/Id/ImporterId/ModuleType/Code/Include/Exclude/QueryKey/QueryValue/CleanUrl等并做了两条重要的合法性校验importerId只能用于resolveIdassertNoImporterId会检查每个 filter 表达式树若在其他 Hook如load/transform/renderChunk中使用了importerId直接抛错The importerId filter can only be used with the resolveId hook见 bindingify-hook-filter.ts。resolveId的字符串idfilter 不被支持因为resolveId收到的id是 import 语句里的原始写法通常不是绝对路径glob 与之无意义必须使用RegExp否则抛错A string id filter is not supported for the resolveId hook见同文件 L141-L166。resolveId、load、transform三个 Hook 的绑定函数分别在bindingifyBuildStart/bindingifyResolveId/bindingifyLoad/bindingifyTransform中把options.filter转交给对应的bindingify*Filter函数并把结果作为filter字段与plugin、meta一起放入BindingPluginOptions见 bindingify-build-hooks.ts。组合过滤器Composable Filters对于更复杂的过滤逻辑还可以直接传入TopLevelFilterExpression[]使用rolldown/pluginutils导出的组合函数如and、or、not、id、importerId、moduleType、code、query、include、exclude、queries来构建表达式树。例如import { and, id, include, moduleType } from rolldown/pluginutils; export default function myPlugin() { return { name: my-plugin, transform: { filter: [include(and(id(/\.ts$/), moduleType(ts)))], handler(code, id) { // 仅当 id 匹配 /\.ts$/ 且 moduleType 为 ts 时调用 return transformedCode; }, }, }; }需要说明的是组合过滤器目前仅适用于 Rolldown 插件Vite 与 unplugin 中暂不支持且字符串id在组合表达式中按“精确相等”匹配而非 glob。为第三方插件注入 filterwithFilter 工具如果你使用的插件没有自带 filter但又想限制其 Hook 的触发范围Rolldown 提供了withFilter辅助函数从 plugin/with-filter.ts 导出import yaml from rollup/plugin-yaml; import { defineConfig } from rolldown; import { withFilter } from rolldown/filter; export default defineConfig({ plugins: [ // 仅对以 .yaml 结尾的模块运行 yaml 插件的 transform Hook withFilter(yaml({}), { transform: { id: /\.yaml$/ } }), ], });withFilter会按插件name支持字符串精确匹配与RegExp模式定位目标插件然后改写其transform/resolveId/load三个 Hook若已是对象形式则覆盖filter若是函数形式则包装成{ handler, filter }对象见 with-filter.ts。sequential已废弃的 Rollup 兼容选项sequential是对象式 Hook 的一个**已废弃Deprecated**属性sequential?: boolean;它仅仅是为了兼容 Rollup 的插件类型而存在。在 Rolldown 中Hook 的执行方式始终等价于sequential: true——即所有 Hook 都会以串行顺序依次等待执行不存在sequential: false的并行语义。这一点在类型注释中有明确说明见 plugin/index.ts/** * deprecated * this is only for rollup Plugin type compatibility. * hooks always work as sequential: true. */ sequential?: boolean;编写插件时无需也不应依赖该选项控制并行行为保留它只是为了能让现有 Rollup 插件代码在不改类型的情况下直接运行在 Rolldown 上。如果你想了解 Rolldown 到底支持哪些 Hook 以何种模式sync/async、sequential/parallel/first运行可以参考FunctionPluginHooks中每个 Hook 的kind注释以及FirstPluginHooks/SequentialPluginHooks/ParallelPluginHooks三个类型别名见 plugin/index.ts。小结与建议何时用对象形式需要给 Hook 附加order调整多插件执行顺序或filterRust 侧预筛、减少跨语言调用时把回调放入handler即可其余场景函数简写完全等价。order的选择pre适合需要在其他插件之前介入的场景如最先拦截解析、最先做代码注入post适合收尾类逻辑同优先级下按配置顺序执行。filter的选择优先用对象形式 filter代替 handler 内部if判断尤其适合resolveId/load/transform这三个高频 Hook注意resolveId的id只接受RegExptransform还可以按moduleType与code过滤。sequential不要用它是纯兼容字段Rolldown 中 Hook 始终串行执行。相关源码与文档的进一步阅读入口object-hook.md、plugin/index.ts、bindingify-plugin.ts、bindingify-hook-filter.ts、hook-filter.ts、with-filter.ts、docs/apis/plugin-api/hook-filters.md。【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考