Rolldown 未支持特性测试清单全解读:从 Rollup 兼容性测试窥探实现边界
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
本篇文章聚焦 Rolldown 仓库中一份特殊的“豁免台账”——ignored-by-unsupported-features.md。它记录了 Rolldown 在运行 Rollup 官方测试套件时,因尚未支持或未完全兼容的插件能力、输出选项与语言特性而被跳过的全部测试用例。读完本文,你将理解这份清单的加载与执行机制、测试 ID 的编码规则、321 条被豁免用例背后的三大类兼容性差距,以及如何用它追踪 Rolldown 与 Rollup 行为对齐的进度。
这份清单的定位:兼容性测试的“未支持特性”豁免台账
Rolldown 的目标是与 Rollup 保持行为对齐,其验证手段并非另起炉灶,而是直接运行 Rollup 自己的测试套件。仓库中的 packages/rollup-tests/README.md 明确写道:packages/rollup-tests/test下的每个测试用例都会代理到项目根目录rollupgit submodule 中的对应测试,从而在 Rolldown 上复跑 Rollup 的全部测试。
然而,Rollup 与 Rolldown 的差距并非一蹴而就,因此测试需要分层豁免。在packages/rollup-tests/src目录下,共存在四种“跳过”机制,各自针对不同的差距类型:
| 豁免文件 | 含义 |
|---|---|
| ignored-tests.js | 已迁移到其他位置、依赖第三方插件、测试基础设施差异,或属于“预期行为差异”的用例 |
| ignored-by-unsupported-features.md(本文主题) | 因 Rolldown 尚未支持/未完全兼容某项特性而被跳过的用例 |
| ignored-treeshaking-tests.js | 与 tree-shaking 相关、暂不参与对齐的用例 |
| ignored-passed-snapshot-different-tests.js | 行为已通过、但输出快照与 Rollup 不同的用例 |
从当前 status.md 的统计可以看到这套体系的全貌:
| 状态分类 | 数量 |
|---|---|
| failed | 0 |
| skipFailed | 296 |
| ignored | 106 |
| ignored(unsupported features) | 321 |
| ignored(treeshaking) | 327 |
| ignored(behavior passed, snapshot different) | 163 |
| passed | 1214 |
也就是说,在总计约 2400 余条 Rollup 测试中,已有 1214 条完全通过,而“未支持特性”豁免占据了 321 条——这是仅次于 tree-shaking 豁免的第二大差距来源,也是理解 Rolldown 当前实现边界最直接的入口。
清单如何被加载与执行:从 Markdown 到测试跳过
这份 Markdown 并不是给人看的静态说明,它同时是机器可读的豁免规则文件。核心解析逻辑位于 packages/rollup-tests/src/intercept/utils.js:
function loadUnsupportedFeaturesIgnoredTests() { const unsupportedIgnoredTests = [] const content = fs.readFileSync(path.join(__dirname, '../ignored-by-unsupported-features.md'), 'utf-8') const matches = content.match(/- (.*)/g) if (matches) { for (const id of matches) { unsupportedIgnoredTests.push(id.replace('- ', '').trim()) } } return unsupportedIgnoredTests }解析规则非常朴素:凡是文件中以-开头的行,其内容即被视为一个测试 ID。随后的shouldIgnoredTest(同文件 L67-L69)会判断某个测试 ID 是否命中四种豁免集合之一:
function shouldIgnoredTest(id) { return ignoredTests.has(id) || ignoredSnapshotDifferentTests.has(id) || ignoredTreeshakingTests.has(id) || unsupportedFeaturesIgnoredTests.find((test) => test.includes(id)) }实际跳过动作发生在测试运行器的钩子中。update-test-status.js 的beforeEach会在每个用例执行前检查shouldIgnoredTest(id),命中则直接this.currentTest?.skip();afterEach则统计通过/失败数量,最终将结果写入status.json与status.md,并在after钩子中强制process.exit(0)以避免残留的 Rust 进程阻塞测试结束。运行与更新状态的命令来自 justfile:just test-node-rollup运行测试,just test-node-rollup --update运行并刷新状态文件(注意--update与--grep互斥)。
测试 ID 的编码规则
要读懂清单中的每一行,必须先理解测试 ID 的生成方式。见 utils.js:
function calcTestId(test) { const paths = test.titlePath() return paths.join('@') }测试 ID 由 Mocha 的test.titlePath()数组以@拼接而成,因此形如rollup@function@preload-module: allows pre-loading modules via this.load,含义为:测试套件根(rollup)@ 测试目录(function)@ 用例名: 用例描述。常见的一级目录包括:
function:构建行为与运行期行为测试(对应 test/function/index.js 代理逻辑)form:输出格式/渲染快照测试hooks:插件钩子执行语义测试sourcemaps:sourcemap 相关测试chunking form:代码分割下的输出格式测试cli、watch、misc、typescript等其他套件
部分 ID 末尾带@generates es(或cjs、system)后缀,表示该用例是同一目录在不同输出格式下的变体,例如rollup@form@compact: supports compact output with compact: true@generates es。
插件相关未支持特性(Plugin related)
清单第一部分记录的是插件体系中的兼容性缺口,共 17 组。这些特性主要涉及PluginContext能力、插件钩子返回值契约与并行插件机制,是生态插件能否无缝迁移的关键。
钩子参数与返回值契约的差异
- The
NormalizedOptionsat hooks is not compatible with rollup:options/outputOptions 钩子中拿到的是未完全规范化(Normalized)的选项对象,影响options-hook、output-options-hook两个用例——它们依赖在钩子中读取并修改规范化后的完整选项。 - The
loadhook returnastis not supported:load钩子返回自定义 AST(ast字段)目前不被支持,豁免了uses-supplied-ast、custom-ast。 - The
resolveIdhookresolvedByis not supported:resolveId返回对象中的resolvedBy字段尚未实现,豁免validate-resolved-by-logic。 - The
shouldTransformCachedModulehook is not supported:缓存模块变换钩子缺失,豁免plugin-error-should-transform。 - The
resolveDynamicImporthookspecifier: AstNodenot supported:动态导入解析钩子无法接收原始 AST 节点,豁免dynamic-import-unresolvable、dynamic-import-expression。 - The
renderDynamicImport/resolveImportMeta/shouldTransformCachedModulehooks not supported:输出期动态导入渲染与 import.meta 解析钩子缺失,豁免enforce-plugin-order。
插件编排与并行语义
- The plugin
sequentialis not supported:sequential: true声明(保证并行钩子的顺序执行)尚未实现,豁免enforce-sequential-plugin-order(function 套件)与 watch 模式下的同类用例。 - The
renderDynamicImporthook not supported:自定义动态导入处理钩子缺失,豁免custom-dynamic-import-no-interop(@generates es)。
PluginContext 能力缺口
- The
PluginContext.parsedoes not supportallowReturnOutsideFunctionoption:上下文解析不支持在函数外返回语句,豁免parse-return-outside-function。 - The
PluginContext.cacheis not supported:插件级缓存 API 完全缺失,共豁免 7 条用例:匿名插件对 cache 的 delete/get/has/set(各 1 条)、重名插件无 cache key 时访问缓存、以及 2 条与 transform 缓存交互的 hooks 用例。 - The
PluginContext.loadis not fully supported:this.load预加载能力不完整,豁免preload-cyclic-module、preload-module、module-side-effects@writable、modify-meta——后两者涉及在resolveId阶段加载入口模块并修改ModuleInfo的语义。 - The
maxParallelFileOpsis not supported:并行文件操作数上限选项缺失,豁免max-parallel-file-operations的 default/error/infinity/set/with-plugin 五条用例。 - The
PluginContext.emitFileemit chunk is only supported partially:通过emitFile发射 chunk 仅部分支持,主要缺口集中在隐式依赖(implicit dependencies)校验与文件名可用性时序,共豁免 17 条:implicit-dependencies@*系列(7 条)、emit-file@set-asset-source-chunk、emit-file@modules-loaded、emit-file@invalid-chunk-id、三个chunk-filename-not-available*(buildEnd/renderStart/常规)、file-references-in-bundle与hooks@caches chunk emission in transform hook。 - The
PluginContext.emitFileemit prebuilt chunk is not supported:发射预构建 chunk(直接提供 code/filename)缺失,豁免prebuilt-chunk、invalid-prebuilt-chunk-filename、invalid-prebuit-chunk-code。 - The
PluginContext.setAssetSourceis not supported:资源源码设置 API 缺失,豁免 9 条用例,包括空资源源码校验(invalid2/3/4)、transform 钩子中设置、outputOptions 钩子中设置、重复设置(twice/twice2)、非法 id,以及hooks@keeps emitted ids stable between runs。 originalFileName/originalFileNamesis not supported properly:发射资源时回传原始文件名到其他钩子的能力不完善,豁免deprecated@emit-file@original-file-name、original-file-name、original-file-names三条。import.meta.ROLLUP_FILE_URL_OBJ_*is not supported:以 URL 对象形式引用发射资源的占位符缺失,豁免resolve-file-url-obj(@generates cjs与@generates es两条)。
选项相关未支持特性(Options related)
第二部分针对的是输入/输出选项层面的差距,共 18 组。这些选项大多属于构建产物形态控制能力,对追求“开箱即用替代 Rollup”的使用者影响最直接。
输出格式与代码形态选项
- The
output.formatsystemjs is not supported:SystemJS 输出格式整体缺失,是单组豁免量最大的选项类缺口,共 20 条用例,覆盖system-comments、system-default-comments、system-export-declarations、system-export-destructuring-declaration、system-export-rendering(-compact)、system-module-reserved、system-multiple-export-bindings、system-null-setters、system-reexports、system-semicolon、system-uninitialized、import-namespace-systemjs、modify-export-semi等全部渲染细节。 - The
format: amdnot supported:AMD 格式的选项校验缺失,豁免amd-auto-id-id、amd-base-path-id、amd-base-path三条(均涉及amd.autoId/amd.basePath/amd.id的组合冲突校验)。 - The
output.compactis not supported:紧凑输出模式缺失,豁免inlined-dynamic-namespace-compact、compact、compact-multiple-imports(@generates es)、form@compact(@generates es)。 - The
output.validateis not supported:输出代码语法校验选项缺失,豁免validate-output。 - The
output.interopis not supported:CJS/外部模块 interop 策略完全未实现,是第二大的选项类豁免组,共 16 条,覆盖interop-auto-live-bindings、interop-auto-no-live-bindings、interop-default-conflict、interop-default-only*系列、interop-default、interop-esmodule、invalid-interop、deconflicts-interop、interop-per-dependency*与interop-per-reexported-dependency。 - The
output.generatedCodeis not supported:产物代码风格选项缺失,豁免 arrow-functions、const-bindings、object-shorthand、reserved-names-as-props 四个子项的 true/false 全组合(function/form 双套件)及unknown-generated-code-value。 - The
output.generatedCode.presetis not supported:代码风格预设缺失,豁免generated-code-presets@es2015、@es5、preset-with-override与unknown-generated-code-preset。注意在测试代理 test/function/index.js 中,Rolldown 默认使用generatedCode.preset: 'es2015',而测试侧统一强制为es5以保证对比基准一致。 - The
output.generatedCode.symbolsis not supported properly:Symbol.toStringTag相关产物符号生成不完善,豁免name-conflict-symbol、namespace-tostring@*系列(dynamic-import-default-mode/dynamic-import/external-namespaces/property-descriptor)共 4 条。 - The
output.sourcemapBaseUrlis not compatible yet:sourcemap 基础 URL 前缀选项不兼容,豁免sourcemap-base-url-invalid、sourcemap-base-url-without-trailing-slash(@generates es)、sourcemap-base-url(@generates es)。
模块组织与分块选项
- The
output.preserveModulesis not compatible yet:保留模块结构的输出模式尚不兼容,豁免 12 条用例:preserve-modules-default-mode-namespace、circular-preserve-modules、missing-export-preserve-modules、preserve-modules-circular-order、preserve-modules@*校验系列(invalid-default-export-mode/invalid-no-preserve-entry-signatures/invalid-none-export-mode/manual-chunks/mixed-exports)、synthetic-named-exports@preserve-modules、circular-namespace-reexport-preserve-modules。 - The
output.manualChunksis not compatible:手动分块能力不兼容,豁免 8 条:manual-chunks-conflict、manual-chunks-include-external-modules(-3)、manual-chunks-info、circular-namespace-reexport-manual-chunks、emit-chunk-manual-asset-source、emit-chunk-manual、manual-chunks-order。 - The
output.treeshake.presetis not supported:tree-shaking 预设缺失,豁免unknown-treeshake-preset。 - The
output.treeshake.moduleSideEffectis not compatible with rollup:moduleSideEffects与插件resolveId的交互语义不兼容,豁免module-side-effects@resolve-id-external与module-side-effects@resolve-id。
输入侧选项与产物 API
- The
input.perfandbundle.getTimings()is not supported:性能计时选项与getTimings()API 缺失,豁免adds-timings-to-bundle-when-codesplitting、adds-timings-to-bundle。 - The
input.moduleContextis not supported:模块级this上下文定制选项缺失,豁免custom-module-context-function、custom-module-context(@generates es)。 - The
Bundle.cacheis not supported:bundle对象的cache/modules数组信息缺失,豁免module-tree(#903)、has-modules-array。 - The
ModuleInfois not compatible with rollup:插件可见的模块信息对象不兼容,豁免 9 条:plugin-module-information(-no-cache)、module-parsed-hook、has-default-export、context-resolve、check-exports-exportedBindings-as-a-supplementary-test、load-resolve-dependencies(importedIdResolutions)、resolve-relative-external-id。 - The chunk information is not compatible with rollup:chunk 级信息不兼容,豁免
form@addon-functions(@generates es)与hooks@supports generateBundle hook including reporting rendered exports and source length(涉及modules.dep.renderedExports/removedExports)。
功能特性相关未支持(Features)
第三部分记录的是语言特性与运行期语义层面的差距,共 14 组,是三类中粒度最细、最接近 ECMAScript 规范的部分。
- The
syntheticNamedExportsis not supported:合成具名导出特性整体缺失,是全文最大的单组豁免,共 28 条,覆盖入口/动态导入/命名空间/循环依赖/跨 chunk 去冲突/回退导出(fallback)/in运算符优化等全部语义。 - Import Assertions is not supported:
assert形式的导入断言缺失,豁免 6 条:plugin-assertions-this-resolve(两条)、warn-assertion-conflicts、warn-unresolvable-assertions、deprecated@removes-dynamic-assertions、deprecated@removes-static-attributes。 - Import attributes is not supported:
with形式的导入属性缺失,共 21 条,覆盖静态/动态导入属性的保留与移除、插件在resolveId/resolveDynamicImport中读写属性、resolveFileUrl/resolveImportMeta附加属性、configure-file-url、以及 deprecated 的load/transform返回属性行为。 - Source phase import is not supported:
import source阶段导入缺失,豁免 5 条:source-phase-imports-external、source-phase-dynamic-import-error(-resolved)、source-phase-format-unsupported、source-phase-import-error。 - watch behavior is not compatible yet:watch 模式行为不兼容,豁免
hooks@allows to enforce plugin hook order in watch mode。 - escaping external id is not supported:外部模块 ID 的引号转义处理缺失,豁免
form@quote-id(@generates es)。 - remove
use strictfrom function body:从函数体中移除use strict指令的行为缺失,豁免function-use-strict-directive-removed。 - The namespace object is not compatible with rollup:命名空间对象语义不兼容(null 原型、冻结、
arguments占位、动态导入默认导出模式、外部实时绑定等),共 13 条,包括namespaces-have-null-prototype、namespaces-are-frozen、namespace-override、escape-arguments、dynamic-import-only-default、dynamic-import-default-mode-facade、chunking-duplicate-reexport、namespace-tostring@interop-property-descriptor、external-dynamic-import-live-binding(-compact)、no-external-live-bindings(-compact)。 hasOwnPropertyexport is not handled properly:导出名为hasOwnProperty时的语义处理不完善,豁免re-export-own。__proto__export is not properly handled:导出名为__proto__时与 CJS 转译器的交互不完善,豁免cjs-transpiler-re-exports-1、cjs-transpiler-re-exports(均@generates cjs,涉及output.externalLiveBindings与output.reExportProtoFromExternal)。- source map combine logic does not support coarse sourcemap well enough:粗粒度 sourcemap 的合并逻辑不足,豁免
combined-sourcemap-3(@generates es)。 strictDeprecationsoption is not supported:严格弃用告警选项缺失,豁免deprecations@*系列 7 条(externalImportAssertions、asset-filename-name、asset-filename-originalfilename、asset-name-in-bundle、asset-originalfilename-in-bundle、asset-render-chunk-originalfilename-in-bundle、asset-render-chunk-name-in-bundle)。- The error/warning information is not compatible with rollup:错误/警告的信息载体不兼容(错误码、
cause属性、日志钩子语义),豁免 8 条,例如banner-and-footer(期望ADDON_ERROR实得PLUGIN_ERROR)、conflicting-reexports@named-import(期望AMBIGUOUS_EXTERNAL_NAMESPACES实得MISSING_EXPORT)、logging@handle-logs-in-plugins、supports renderError hook等。 - The error/warning not implement:错误/警告类型本身尚未实现,是清单中条目最多的单组(约 55 条),几乎每一行都标注了“期望的 Rollup 错误/警告码 vs Rolldown 实际产物”,例如
INVALID_OPTIONvsGenericFailure、VALIDATION_ERRORvsInvalidArg、MISSING_EXPORT应为警告而非错误、EMPTY_BUNDLE/NAMESPACE_CONFLICT/THIS_IS_UNDEFINED/SOURCEMAP_ERROR/INVALID_ANNOTATION等告警的缺失或错位。部分条目还标注了上游来源,如ast-validations@redeclare-import-var关联 oxc-project/oxc issue #15961。
从清单到实现:兼容性差距的观测与演进
清单是"活文档",也是 roadmap
这份清单不是一次性产物。任何开发者都可以在修复某项特性后,从文件中删除对应的-行,使相关测试重新进入执行队列;反向地,新增未支持特性时也应把对应测试 ID 追加进来。由于解析逻辑完全基于-前缀的行匹配,维护时只需遵守“一行一个测试 ID”的格式约定即可,无需改动 intercept/utils.js 的代码。
测试代理与格式变体
被豁免的用例大多仍由 test/function/index.js 等代理文件按 Rollup 原样加载(其测试样本目录指向../../../../rollup/test/function/samples,即根目录 git submodule)。代理层还会为对比公平性注入一些默认值,例如禁用inlineConst优化、默认dynamicImportInCjs: true、强制generatedCode.preset: 'es5'等,因此清单中@generates es/cjs/system后缀标识的变体正是同一用例在多格式参数化下的独立 ID。
如何自行验证当前状态
在项目根目录执行just test-node-rollup即可复现测试;使用just test-node-rollup --update会在结束后刷新 status.json 与 status.md。运行期间,update-test-status.js会为每个用例打印其 ID(console.log(id)),便于对照清单逐条核验:某个用例若从“ignored(unsupported features)”转为通过,通常意味着对应特性已落地。
结语
ignored-by-unsupported-features.md表面上是一份被跳过的测试列表,实际上是一张经过机器验证的兼容性差距地图:插件体系(PluginContext、emitFile、缓存、钩子契约)、选项体系(SystemJS/AMD 格式、interop、generatedCode、preserveModules/manualChunks)、语言特性(syntheticNamedExports、import attributes、source phase import、namespace 语义)三大维度的边界被 321 条用例精确锚定。对于使用 Rolldown 的开发者,它是排查“为什么这个 Rollup 选项/插件行为不一致”的权威索引;对于贡献者,它则是按图索骥、逐项消除差距的路线图——每删掉一行-开头的内容,就意味着 Rolldown 与 Rollup 的行为对齐又前进了一步。
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考