Rolldown 死代码消除与 Tree Shaking 能力全景:基于 esbuild 兼容性测试套件的分析
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
导读
dce.md是 Rolldown 仓库中 esbuild 兼容性测试(snap-diff)在死代码消除(DCE / Dead Code Elimination)与 Tree Shaking维度上的执行结果摘要,它按Failed Cases、Passed Cases、Ignored Cases三类逐条记录了 Rolldown 与 esbuild 在 DCE 行为上的对齐程度。本文以该摘要为骨架,结合 crates/rolldown/tests/esbuild/dce/ 下的真实测试夹具、diff.md 差异快照以及 reasons.ts 中的原因标注,系统梳理 Rolldown 当前在 DCE 领域已覆盖的能力、已知的次优实现与主动忽略的行为边界。读完本文,你将能够快速定位 Rolldown 在 tree shaking、常量折叠、sideEffects处理、@__PURE__注释等场景下的现状,并理解这套 esbuild 兼容性测试的运作方式。
摘要文档在测试体系中的位置
dce.md位于 scripts/src/esbuild-tests/snap-diff/summary/,与default.md、loader.md、packagejson.md、splitting.md、ts.md等并列,是 snap-diff 流程按测试分类生成的多个维度摘要之一。该目录下还有:
stats/stats.md:汇总统计;diff.ts、snap-parser.ts、runner.ts、download-snapshots.ts、rewrite.ts等:负责下载 esbuild 快照、解析产物、生成差异与摘要的脚本;gen-tests.ts:生成测试用例;reasons.ts:集中登记“失败原因”“不支持原因”“忽略原因”三张映射表。
每一份摘要的结构都由reasons.ts中的三张表驱动:命中failedReasons的用例归入Failed Cases,命中ignoreReasons的归入Ignored Cases,其余进入Passed Cases。因此,本文解读的dce.md本质上是“esbuild 官方 DCE 测试集在 Rolldown 上的行为快照”,其背后有大量可直接查阅的 diff 与配置证据。
Failed Cases:当前未对齐的 4 个用例
摘要文档共记录了 4 个失败用例,失败原因分为“上游依赖(oxc)待修复”与“Rolldown 自身次优实现”两类,全部指向dce/目录下的对应夹具。
dce_of_iife:IIFE 未解包
- 摘要:crates/rolldown/tests/esbuild/dce/dce_of_iife/diff.md
- 原因标注:
https://github.com/oxc-project/oxc/issues/17480 and sub optimal: IIFEs are not unwrapped in some cases
该用例通过 _config.json 声明两个入口remove-these.js与keep-these.js,用于验证无副作用的立即执行函数表达式(IIFE)应当被整体移除。从diff.md的差异快照可以看到具体分歧:
- esbuild 将
keepMe();(一条独立调用语句)原样保留,而 Rolldown 将其输出为(() => { keepMe(); })();; - 对于
var keepThis = stuff(); keepThis();,esbuild 保留为两条语句,Rolldown 则折叠为(() => stuff())()();; - 该用例同时验证了
/* @__PURE__ */标注的调用可被移除、带默认参数的箭头函数不可移除等语义,Rolldown 在这些点上与 esbuild 一致。
结论是:Rolldown 在部分场景下不会把 IIFE“解包”为裸语句,导致输出体积略大于 esbuild,属于次优实现(sub optimal),其修复还部分受制于 oxc 项目的 issue 17480。
tree_shaking_lowered_class_static_field:降级后的类静态字段可被进一步移除
- 摘要目录:crates/rolldown/tests/esbuild/dce/tree_shaking_lowered_class_static_field/
- 原因标注:
sub optimal: REMOVE_ME class can be removed
当类静态字段被降级(lowering)为赋值语句后,本应有机会连同其声明一起被 tree shaking 移除(测试中以REMOVE_ME命名该类)。Rolldown 目前仍保留该类,说明在“降级产物再参与 DCE”这一链路上的优化深度还不及 esbuild。值得一提的是,同族用例tree_shaking_lowered_class_static_field_assignment与tree_shaking_lowered_class_static_field_minified已进入 Passed 列表,说明该族能力已部分覆盖。
tree_shaking_react_elements:React.Fragment 未被移除
- 摘要目录:crates/rolldown/tests/esbuild/dce/tree_shaking_react_elements/
- 原因标注:
sub optimal: React.Fragment should be removed
该用例考察 JSX 转换后产生的React.Fragment引用是否能在未被使用时被消除。当前 Rolldown 会将其保留,属于可优化的空间,与 oxc 的 JSX 转换/常量折叠能力相关。
tree_shaking_unary_operators:一元运算符场景被拒绝
- 摘要目录:crates/rolldown/tests/esbuild/dce/tree_shaking_unary_operators/
- 原因标注:
rejected due to https://github.com/rolldown/rolldown/issues/7009
该用例涉及with语句相关语义(issue 7009 是with语句被拒绝的登记项,同样的原因也出现在default/comment_preservation、ts/ts_print_non_finite_number_inside_with等用例上)。属于主动拒绝而非实现缺陷,详见下文“Ignored Cases”一节对拒绝机制的说明。
Passed Cases:已对齐的能力版图
通过用例是摘要的主体,约 90 个,展示了 Rolldown 在 DCE 领域已具备的广度。按技术主题归纳如下。
loader 产物中的未使用移除
- base64_loader_remove_unused:base64 加载的资源未使用时被移除;
- data_url_loader_remove_unused:data URL 资源同理;
- file_loader_remove_unused:文件加载资源同理;
- json_loader_remove_unused:JSON 加载结果未使用时被移除;
- text_loader_remove_unused:文本加载资源同理。
这些用例共同验证:无论资源以何种 loader 内联进包,只要其导出未被消费,就能被整段剔除。
跨模块常量折叠与常量值内联
- cross_module_constant_folding_number、cross_module_constant_folding_string、cross_module_constant_folding_computed_property_name:跨模块的
const常量在编译期完成折叠,包括计算属性名场景; - const_value_inlining_bundle、const_value_inlining_no_bundle、const_value_inlining_direct_eval:常量值内联的三种形态——打包、不打包以及存在直接
eval(内联需谨慎)的场景。
typeof相关的守卫条件消除
dce_type_of家族覆盖了typeof与字符串常量比较后的分支消除,包括:
- dce_type_of(基础形态);
- dce_type_of_equals_string、dce_type_of_equals_string_guard_condition、dce_type_of_equals_string_mangle:
typeof x === "string"形式的守卫条件消除,以及标识符压缩(mangle)后的表现; - dce_type_of_compare_string_guard_condition:比较式守卫条件。
package.json的sideEffects语义
这是通过用例中最庞大的族群(约 30 个),覆盖三种取值形态:
sideEffects: false:整包视为无副作用。代表性用例包括 package_json_side_effects_false_remove_bare_import_es6、package_json_side_effects_false_remove_bare_import_common_js(裸导入被移除)、package_json_side_effects_false_keep_named_import_es6、package_json_side_effects_false_keep_star_import_es6(具名/星号导入被保留);还包括中间文件链(chain/diamond/unused/used)四种拓扑(package_json_side_effects_false_intermediate_files_chain_all 等),以及跨平台路径分隔符(package_json_side_effects_false_cross_platform_slash)和 node_modules 内免警告(package_json_side_effects_false_no_warning_in_node_modules_issue999)。sideEffects: ["*.css", ...](数组 + glob):package_json_side_effects_array_glob 验证 glob 匹配;package_json_side_effects_array_remove 与 package_json_side_effects_array_keep 验证按 main/module 字段命中的保留与移除组合。sideEffects: true:package_json_side_effects_true_keep_es6、package_json_side_effects_true_keep_common_js 验证默认全保留。
此外还有 package_json_side_effects_keep_export_default_expr、package_json_side_effects_nested_directory_remove 等边界用例,说明这套sideEffects语义已被系统性地对齐。
纯注释(/* @__PURE__ */与// @__PURE__)
no_side_effects_comment 是 DCE 目录下夹具最多的用例之一,通过 多条输入文件 覆盖函数声明/箭头函数/导出默认函数等十余种放置形态。同族用例包括:
- no_side_effects_comment_ignore_annotations:
ignoreAnnotations开关; - no_side_effects_comment_minify_whitespace:压缩空格后注释仍生效;
- no_side_effects_comment_type_script_declare:TS
declare场景; - no_side_effects_comment_unused_calls:未使用调用的移除。
配套的 remove_unused_pure_comment_calls、pure_calls_with_spread 与 inline_empty_function_calls 则验证了更细的纯调用与空函数调用处理。
语法降级与压缩下的 DCE
- dce_class_static_blocks 与 dce_class_static_blocks_minify_syntax:类静态块及其压缩形态;
- dce_of_using_declarations:
using声明; - dce_of_decorators 与 dce_of_experimental_decorators:两种装饰器语法;
- dce_of_destructuring:解构;
- dce_of_negated_bigints:取负的 BigInt 字面量折叠;
- dce_template_literal:模板字符串折叠;
- multiple_declaration_tree_shaking 与 multiple_declaration_tree_shaking_minify_syntax:多重声明消除;
- preserve_directives_minify_bundle、preserve_directives_minify_iife、preserve_directives_minify_pass_through:压缩时对指令(directive)的保留策略。
控制流与死代码
- dead_code_following_jump:跳转后的死代码;
- dead_code_inside_empty_try:空 try 块内的死代码;
- remove_code_after_label_with_return 与 drop_label_tree_shaking_bug_issue3311:带标签语句;
- drop_labels:标签消除;
- remove_trailing_return:尾部
return消除; - tree_shaking_binary_operators:二元运算常量折叠。
函数内联
inline_identity_function_calls、inline_function_call_behavior_changes、inline_function_call_for_init_decl、nested_function_inlining_with_spread、top_level_function_inlining_with_spread 共同验证了空函数、恒等函数在init声明、嵌套、spread参数等形态下的内联与行为保持。
Symbol、import 与导出
- dce_of_symbol_ctor_call、dce_of_symbol_for_call、dce_of_symbol_instances:
Symbol相关调用语义; - dce_var_exports、dce_of_expr_after_keep_names_issue3195、dce_of_iterator_superclass_issue4310:与已知 issue 关联的边界行为;
- import_re_export_of_namespace_import、tree_shaking_import_identifier、tree_shaking_in_esm_wrapper:导入标识符与命名空间再导出;
- remove_unused_imports 与 remove_unused_imports_eval、remove_unused_imports_eval_ts:未使用 import 的移除,以及存在
eval时的保守行为; - remove_unused_import_meta:
import.meta未使用时移除; - tree_shaking_no_bundle_cjs、tree_shaking_no_bundle_esm、tree_shaking_no_bundle_iife:三种输出格式下“不打 bundle”时的 tree shaking;
- tree_shaking_object_property、tree_shaking_class_property、tree_shaking_class_static_property:对象属性、类实例属性与静态属性的摇树;
- disable_tree_shaking:显式关闭 tree shaking 时的行为。
从测试夹具的 _config.json 可以看到,每个用例通过input数组声明入口,部分用例带expectExecuted: false标记(表示该用例不要求运行时执行,只比对静态产物),这套元数据直接驱动快照对比流程。
Ignored Cases:主动忽略与暂不支持的行为
摘要末尾的 Ignored Cases 分两段:普通忽略与“not supported”忽略,两者在 reasons.ts 中分别由ignoreReasons与notSupportedReasons登记。
对齐 esbuild 的“hacky”行为(普通忽略)
- package_json_side_effects_array_keep_main_implicit_main、package_json_side_effects_array_keep_module_implicit_main、package_json_side_effects_array_keep_module_use_main:原因标注为 “this is a hacky behavior of esbuild”(esbuild 的取巧行为),Rolldown 选择不模仿;
- remove_unused_no_side_effects_tagged_templates:涉及 compiler-notations-spec(
@__NO_SIDE_EFFECTS__规范草案)的 issue 8,规范尚未定稿,故忽略。
因 CSS 打包能力缺失而忽略
tree_shaking_js_with_associated_css及其 6 个变体(export_star_side_effects_false、re_export_side_effects_false、unused_nested_import_side_effects_false及各自的_only_js版本)全部以 “CSS bundling is not supported”(issue 4271)为原因被忽略。这属于明确的能力边界而非缺陷:JS 的 tree shaking 逻辑本身不受影响,只是“JS + 关联 CSS”的整体场景暂不在覆盖范围内。
明确不支持的场景(not supported)
摘要末尾单列了 dead_code_inside_unused_cases:原因标注为 “dce inside unused switch cases is not supported”,即未命中的switch分支内部的死代码消除暂不支持,与reasons.ts中notSupportedReasons的登记一一对应。
如何进一步查看差异细节
当某个用例进入 Failed Cases 时,其对应目录内通常包含diff.md,可直接查看 esbuild 与 Rolldown 产物的逐行差异。以 dce_of_iife/diff.md 为例,其结构为三段:
### esbuild:esbuild 的产物代码块;### rolldown:Rolldown 的产物代码块(并带#region注释标注模块边界);### diff:统一 diff 格式的逐行对比(--- esbuild与+++ rolldown)。
这使得任何差异都能被精确归因到具体语法构造,也为后续优化提供了最小复现单元。Passed Cases 的目录同样保留_config.json与artifacts.snap,可作为回归测试的基线快照。
小结
从dce.md这份摘要可以得出 Rolldown 在 DCE 领域的整体画像:
- 广度已相当可观:loader 产物移除、跨模块常量折叠、
typeof守卫消除、sideEffects三种形态(false / 数组 glob / true)、@__PURE__注释、类静态块、装饰器、using、Symbol 调用、函数内联、多格式 no-bundle 摇树等均已有通过用例佐证; - 剩余差距集中在“次优实现”而非功能缺失:IIFE 未解包、降级类静态字段未再摇掉、
React.Fragment未移除,都属于可进一步压缩输出体积的优化空间; - 行为边界清晰且显式登记:esbuild 的 hacky 行为、CSS 打包缺失、未命中 switch 分支内的 DCE,均在 reasons.ts 中留下原因,便于后续按 issue 跟踪。
对于希望为 Rolldown 贡献 DCE 相关优化、或评估其 tree shaking 能力的读者,建议从本文列出的 4 个 Failed Cases 的diff.md入手:它们既是差距清单,也是可直接运行的回归夹具。
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考