Rolldown 与 Rollup 行为对齐测试全景:从 status.md 读懂 1214 个用例的通过矩阵
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
Rolldown 作为以 Rollup 兼容 API 为目标的 Rust 打包器,其核心工程目标之一就是与 Rollup 保持行为对齐。本文以 packages/rollup-tests/src/status.md 这份测试状态清单为主线,拆解"直接复用 Rollup 官方测试套件来验证 Rolldown"的完整机制:你会看到 1214 个通过用例、296 个暂缓用例、数百个按原因分门别类的忽略用例是如何被统计出来的,以及如何用just test-node-rollup一键复现这套回归测试。
一、status.md 是什么:一张可机器读取的对齐进度表
status.md 是一份由测试运行器自动生成的 Markdown 状态表,全文只有一张两列的表:
| number | |
|---|---|
| failed | 0 |
| skipFailed | 296 |
| ignored | 106 |
| ignored(unsupported features) | 321 |
| ignored(treeshaking) | 327 |
| ignored(behavior passed, snapshot different) | 163 |
| passed | 1214 |
这张表的每一行都对应 status.json 中同一份数据的落地:Rolldown 在"跑 Rollup 官方测试"这件事上的当前达成度,被压缩成了七个数字。读懂它,就等于读懂了这个仓库对"兼容 Rollup"这件事的可量化定义。
各状态字段含义
- failed(0):本轮测试中真正失败的用例数。CI 中该数字必须为 0,否则
check.js会以非零退出码终止进程。 - skipFailed(296):已经被记录在 failed-tests.json 中的"已知失败"用例。它们不会在每轮测试中重复跑,而是直接跳过,从而把 CI 的关注点集中在"有没有新增失败"上。这一点在 check.js 中可以印证:
alreadyFailedTests.has(id)时直接this.currentTest.skip()。 - ignored(106):因"有意为之的行为差异"而忽略的用例,清单集中在 ignored-tests.js。从源码注释可以归纳出六大类原因:已被迁移到其他测试位置的用例、测试基础设施相关的忽略(如
skipIfWindows)、以及预期的行为差异——例如 Rolldown 在bundle.generate/bundle.write时才启动构建、import.meta.urlpolyfill 行为不同、ASSIGN_TO_IMPORT与 Rollup 的ILLEGAL_REASSIGNMENT错误码差异、对 const 重赋值报错而非告警等。 - ignored(unsupported features)(321):因 Rolldown 尚未支持某些特性而忽略的用例,逐条记录在 ignored-by-unsupported-features.md 中,并按特性分组(共 427 行)。从该文件可以清晰看到当前的能力边界:
load钩子返回ast暂不支持、resolveDynamicImport钩子的specifier: AstNode暂不支持、插件sequential排序暂不支持、renderDynamicImport/resolveImportMeta/shouldTransformCachedModule钩子暂不支持、PluginContext.cache暂不支持、PluginContext.parse不支持allowReturnOutsideFunction选项等。这份清单本质上就是 Rolldown 插件 API 的"待办路线图"。 - ignored(treeshaking)(327):与 tree-shaking 行为相关的忽略用例,清单见 ignored-treeshaking-tests.js(同名 JSON 见 ignored-treeshaking-tests.json)。
- ignored(behavior passed, snapshot different)(163):行为已经通过、但输出快照(snapshot)与 Rollup 不一致的用例,见 ignored-passed-snapshot-different-tests.js。这一类别最有价值——它说明功能逻辑是正确的,只是输出细节(如代码生成格式)与 Rollup 存在差异,属于"对齐度已经很高、只差快照收敛"的部分。
- passed(1214):完整通过且快照一致的用例,是对齐度的硬指标。
从 update-test-status.js 的writeTestStatusToMarkdown函数可以看到,status.md的表格格式完全由代码生成:Object.keys(status)遍历状态对象,逐行输出| key | value |。因此不要手工编辑 status.md,任何数字变化都应该通过重新运行测试来更新。
二、这套测试为什么可行:从 submodule 到测试代理
status.md里的数字不是凭空统计出来的,它们来自 Rollup 官方测试套件。核心思路在 README.md 中讲得很清楚:
We aim for behavior alignment with Rollup by running Rollup's own tests against Rolldown.
具体实现方式:
- submodule 提供测试源:仓库根目录下的
rollup目录是一个 git submodule,包含 Rollup 官方测试用例。 - 代理测试文件:packages/rollup-tests/test 下的每个测试文件都代理到 submodule 中对应的测试。例如
test/form/index.js代理 Rollup 的 form 测试,test/function/index.js代理 function 测试,还有 chunking-form、cli、file-hashes、hooks、incremental、leak、load-config-file、misc、sourcemaps、typescript、watch、browser 等目录,覆盖了 Rollup 测试的几乎全部类别。 - 替换底层打包实现:这些测试原本调用的是 Rollup 的 API,而在本仓库中它们被打包进了
@rolldown/binding等 Rolldown 实现之上,从而实现"用 Rollup 的测试用例考验 Rolldown 的实现"。
前置条件:submodule 初始化
运行测试前需要先初始化 submodule:
- 项目 setup 阶段执行
just setup会完成初始化; - 之后每次同步上游,执行
just update-submodule(在 justfile 中它是setup-submodule的别名)更新 submodule 内容。
三、一键运行:just test-node-rollup 的完整调用链
justfile 中定义了运行 Rollup 测试的入口。关键命令如下:
# 运行 Rollup 测试套件(会先构建 Rolldown) just test-node-rollup # 运行并更新测试状态文件(failed-tests.json / status.json / status.md) just test-node-rollup --update两条命令在 justfile 中的定义分别是:
test-node-rollup *args="": build-rolldown just t-node-rollup {{ args }} t-node-rollup *args="": vp run --filter rollup-tests test {{ args }}即:just test-node-rollup会先执行build-rolldown构建原生绑定,再通过 pnpm workspace 的vp run --filter rollup-tests test运行 packages/rollup-tests/package.json 中的测试脚本:
"test": "ROLLUP_TEST=1 mocha --file ./src/intercept/main.js test/test.js"这条命令由三部分组成:
ROLLUP_TEST=1:环境变量,告诉测试基础设施当前运行的是 Rollup 对齐测试(而非 Rolldown 自身测试);mocha:测试运行器;--file ./src/intercept/main.js:mocha 的--file参数,在任何测试用例之前加载 intercept/main.js,从而把状态统计逻辑注入到测试生命周期中。
此外还可以附加 mocha 参数:
# 只运行匹配 grep 的用例(不会做全量状态校验) just test-node-rollup --grep "tree-shaking" # 同时跑 Rolldown 自身测试与 Rollup 对齐测试 just test-node--grep与--update不能同时使用,main.js 中对此有显式校验:Cannot use --update with --grep。
依赖环境的自动化处理
测试脚本前还会执行 setup-node-modules.js,它会把rollup-tests/node_modules符号链接到rollupsubmodule 的node_modules位置,以统一控制依赖版本、保证minimumRelease等版本门槛被满足。这意味着你不需要手动为 submodule 安装依赖,运行测试时链路会自动处理。
四、状态机如何工作:intercept 层的生命周期钩子
status.md的数字是 mocha 钩子一点点"算"出来的,核心逻辑分布在 intercept 目录的四个文件中。整个流程分两种模式:
更新模式(--update,入口 update-test-status.js)
beforeEach:计算当前测试 ID(calcTestId用test.titlePath().join('@')生成,形如rollup@form@jsx@preserves-jsx-text: ...);若命中 ignore 清单则跳过;若命中已知失败清单则计入skipFailed并跳过。同时设置 500ms 超时保护,超时用例会以Test timed out: [id]报错。afterEach:根据测试状态累加failed或passed计数;失败用例的 ID 会被加入alreadyFailedTests集合。after:把失败集合写回 failed-tests.json,把状态对象序列化写入 status.json,并通过writeTestStatusToMarkdown生成 status.md。最后process.exit(0)强制退出,避免 Rust 进程残留导致 mocha 挂起。
校验模式(默认,入口 check.js)
- 同样在
beforeEach/afterEach中统计,但after阶段会与 status.json 中的期望值比对; - 若
failed > 0,输出所有失败详情并以退出码 1 结束; - 若
expectedStatus.skipFailed !== status.skipFailed,或passed数与期望不一致(除非设置了SKIP_PASS_DIFF环境变量),则抛出错误提示:The rollup test status file is not updated. Please run just test-node-rollup --update to update it.; - 使用
--grep时跳过通过数校验,因为筛选后数字必然不完整。
这套"更新—提交—校验"的闭环,保证了 status 文件(包括 status.md)永远与实际运行结果一致——任何改动代码导致的通过数变化,都必须以--update重新生成状态文件后随 PR 提交。
忽略判定逻辑
utils.js 中的shouldIgnoredTest决定一个用例是否被忽略,它聚合了四份清单:
ignored-tests.js导出的ignoreTests(106 项);- ignored-by-unsupported-features.md 中以
-前缀解析出的用例(321 项); ignored-treeshaking-tests.js(327 项);ignored-passed-snapshot-different-tests.js(163 项)。
需要特别注意的是,status.md 中的ignored一行对应的是ignoredTests.size——即只有"有意忽略"那 106 项;而ignored(unsupported features)一行才是loadUnsupportedFeaturesIgnoredTests().length。四类忽略之和为 106 + 321 + 327 + 163 = 917,加上 passed 1214、skipFailed 296、failed 0,合计 2427 个用例,这就是 Rollup 测试套件在本仓库中被度量的总规模。
五、从清单反推兼容性路线图
status.md是"结果",而它背后的清单是"原因"。通过对比不同 ignore 清单,可以反推出 Rolldown 的兼容策略:
- 刻意行为差异(106 项):这是"主动选择不同"的部分。例如 Rolldown 默认
output.dir为dist、构建时机在bundle.generate/bundle.write而非rollup.rollup、错误码体系不同(ASSIGN_TO_IMPORTvsILLEGAL_REASSIGNMENT、PARSE_ERRORvsMISSING_EXPORT)、对 const 重赋值直接报错而非告警等。这些差异多数有对应的 GitHub issue 跟踪(清单注释中保留了 issue 链接的编号),说明是经过讨论后确定的取舍,而不是遗漏。 - 未支持特性(321 项):这是"想做还没做"的部分。ignored-by-unsupported-features.md 按
Plugin related等章节组织,每个章节标题就是一项能力缺口。随着这些特性在 Rust 端落地,对应条目会从这份清单中移除、转入正式测试。 - 快照差异(163 项):这是"行为已通过、输出未对齐"的部分,对齐成本最低、收益也最直接——逻辑正确只是格式化细节不同。
- tree-shaking 差异(327 项):单独归类,说明 tree-shaking 是 Rollup 对齐中占比最大的独立领域,Rolldown 为此维护了独立的忽略清单与更新脚本(package.json 中的
update-treeshaking-failures脚本专门用于更新 ignored-treeshaking-tests.json,配套的专项用例见 test/form/found-tree-shaking-not-align.js)。
六、总结:如何读这份状态表
status.md是 Rolldown 项目里最有信息密度的一张表。作为使用者,你只需要记住三点:
- 看 passed(1214):这是 Rollup 官方用例中"行为与快照双重对齐"的硬指标,占被测总量的约 50%,且仍在增长。
- 看 failed(0)与 skipFailed(296):
failed必须保持为 0;skipFailed是已登记在案的已知失败,只读不重跑。任何将skipFailed转化为failed的改动都意味着回归。 - 看三份大清单:
ignored(unsupported features)(321)与ignored(treeshaking)(327)标注了能力边界与待办路线,ignored(behavior passed, snapshot different)(163)则是最容易通过快照收敛转为passed的增量来源。
想复现这套数据,在完成just setup(含 submodule 初始化)后执行just test-node-rollup即可;改动代码导致数字变化时,用just test-node-rollup --update重新生成 status.md 与 status.json,让这张表始终如实反映 Rolldown 与 Rollup 的行为对齐进度。
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考