news 2026/9/15 17:21:01

Rolldown 的 output.preserveModulesRoot 选项详解:精确控制 preserveModules 模式下的输出目录结构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rolldown 的 output.preserveModulesRoot 选项详解:精确控制 preserveModules 模式下的输出目录结构

Rolldown 的 output.preserveModulesRoot 选项详解:精确控制 preserveModules 模式下的输出目录结构

【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown

导读

output.preserveModulesRoot是 Rolldown(一个使用 Rust 编写、兼容 Rollup API 的 JavaScript/TypeScript 打包器)在preserve modules 模式(即output.preserveModules: true)下用于控制输出目录结构的关键选项。它通过剥离输入模块路径中共享的目录前缀,让产物的目录结构不再受源代码目录深度影响,从而在输出目录迁移、monorepo 多包开发、第三方模块未标记external等场景下保持稳定的产物路径。读完本文,你将掌握该选项的完整配置方法、适用场景、底层实现原理及其与preserveModulesvirtualDirnameinputexternal等选项的协同关系。

一、选项定位:它是preserveModules的目录修剪器

Rolldown 的preserveModules模式与 Rollup 保持行为一致:不再像默认模式那样“尽量少产出 chunk”,而是为每个模块生成独立的 chunk,并使用原始模块名作为文件名。与此同时,tree-shaking 依然生效——未被入口引用、且执行无副作用(side effects)的模块文件会被抑制不产出,非入口模块中未使用的导出也会被移除。

但仅仅保留模块名是不够的:如果两个入口模块分处不同的目录层级(例如src/module.jssrc/another/module.js),它们产出的文件会带着各自的目录结构写入output.dir。此时,preserveModulesRoot的价值就体现出来了——它指定一个“输入模块的根目录路径”,在输出时把该前缀从产物路径中剥离,从而裁剪掉你不想保留的目录层级。

官方对该选项的使用场景说明非常明确:当输出目录结构可能发生变化时,这个选项尤其有用。典型情况包括:

  • 第三方模块未被标记为external:这些模块被打包进来后,其文件路径会被保留到产物中,导致output.dir下出现不受控的深层目录;
  • monorepo 多包相互依赖且未标记external:多个 package 之间互相引用时,产物路径中可能包含各自包的目录前缀,剥离公共根目录可以让输出结构更干净、更可预期。

二、完整配置示例与输出效果

以下示例继承自官方文档,并补充了必要的上下文注释:

import { defineConfig } from 'rolldown'; export default defineConfig({ input: ['src/module.js', 'src/another/module.js'], output: { dir: 'dist', preserveModules: true, preserveModulesRoot: 'src', }, });

设置preserveModulesRoot: 'src'后,输入模块会被输出到如下路径:

dist/module.js // 来自 src/module.js dist/another/module.js // 来自 src/another/module.js

对比不设置该选项的行为:src/module.jssrc/another/module.js的目录结构原本会被原样带入产物,得到dist/src/module.jsdist/src/another/module.jspreserveModulesRoot剥离了公共前缀src/,这正是它名字中 “Root” 的含义——它定义的是“从哪个根目录开始保留结构”。

在 TypeScript 类型定义中,该选项声明为preserveModulesRoot?: string;,位于 output-options.ts,其语义描述为:“在使用 preserve modules 模式时,需要从output.dir中剥离的输入模块目录路径”,文档说明正是通过{@include ./docs/output-preserve-modules-root.md}机制内嵌到该选项的 API 注释中(参见 output-options.ts)。

配套选项速查

选项类型默认值作用
preserveModulesbooleanfalse开启 preserve modules 模式,为所有模块按原始模块名产出独立 chunk(见 output-preserve-modules.md)
preserveModulesRootstring未设置指定从输出路径中剥离的输入模块根目录
virtualDirnamestring'_virtual'插件产出的“虚拟”模块文件的目录名(见 output-options.ts)

三、底层实现:Rolldown 如何在 Rust 中剥离路径前缀

preserveModulesRoot从 JavaScript 侧经 binding 层传递到 Rust 核心。参数绑定路径为:packages/rolldown/src/utils/bindingify-output-options.ts序列化选项 →crates/rolldown_binding/src/utils/normalize_binding_options.rs中的preserve_modules_root: output_options.preserve_modules_root,最终进入NormalizedBundlerOptions(定义于 normalized_bundler_options.rs)。

真正的路径计算发生在生成阶段的generate_chunk_name_and_preliminary_filenames函数中(crates/rolldown/src/stages/generate_stage/mod.rs),核心逻辑可归纳为三步:

  1. 判断模式:仅当self.options.preserve_modulestrue时,才对入口模块走 preserve modules 的命名分支(见 mod.rs)。
  2. 剥离根前缀:读取preserve_modules_root后,通过strip_path_prefix_to_slash函数把该前缀从模块的绝对路径中裁剪掉,得到相对路径(见 mod.rs)。
  3. 失败回退:如果前缀剥离失败(例如模块路径并不以preserveModulesRoot开头),实现会回退为relative_path_to_slash(abs, input_base.as_str())——即退而求其次,基于入口路径的公共基目录(input_base)来计算相对路径,保证产物文件名仍然稳定可用(见 mod.rs)。

此外,源码还处理了路径风格归一化:当模块 id 是“类绝对路径”形式(如/favicon)时,实现会将这种无卷标的根路径锚定到 cwd 所在卷根(Windows 的盘符或 UNC 共享),避免剥离前缀时误吞开头的斜杠,从而把盘符/前导斜杠泄漏进[name](见 mod.rs)。

虚拟文件目录的补充说明

在 preserve modules 模式下,如果插件为了达成某些效果而产出额外的“虚拟”文件,这些文件会被以实际文件形式输出,命名模式为${output.virtualDirname}/fileName.js(默认_virtual/)。preserveModulesRoot只负责裁剪真实模块路径的公共前缀,虚拟文件则由virtualDirname单独管理,两者互不干扰(详见 output-preserve-modules.md)。

四、跨平台与路径规范化:来自测试用例的验证

路径处理最容易在 Windows 与类 Unix 系统之间出现行为差异。仓库中有一个专门的集成测试验证了这一场景:preserve_modules_root_with_slash_normalized_ids(见 crates/rolldown/tests/rolldown/issues/9593/mod.rs)。

该测试构造了一个返回slash 规范化 id(把\统一替换为/,见测试中的resolve_id实现,mod.rs)的 resolve 插件,并以preserve_modules: Some(true)preserve_modules_root: Some("src".into())构建打包配置。它验证了:即使插件提供的模块 id 经过了反斜杠到正斜杠的归一化,preserveModulesRoot的剥离逻辑依然能够正确工作。从源码结构看,这正是通过node_style_absolutestrip_path_prefix_to_slash的组合来兼容“模块 id 保持原生分隔符、而输出路径统一使用/”这两种情形(参见 mod.rs 及注释指向的 internal-docs 说明)。

这一测试同时印证了官方文档开篇的定位:该选项的设计目标就是让产物目录结构“稳定且可预测”,即使源代码目录树或模块解析方式发生变化。

五、使用建议与注意事项

结合官方文档与源码行为,给出以下实操建议:

  1. preserveModules: true成对使用preserveModulesRoot仅在 preserve modules 模式下生效(源码在 mod.rs 处先判断preserve_modules),单独设置不会产生任何效果。
  2. 不要盲目用它做全量格式转换:官方文档明确指出,如果目的是把整个文件结构转换成另一种格式并直接导入,不建议盲目开启 preserve modules——因为 tree-shaking 可能让某些预期中的导出缺失。此时更合适的做法是把所有文件显式加入input选项对象,并可通过 glob 模式动态指定(见 output-preserve-modules.md)。
  3. 优先把相互依赖的包标记为external:文档强调该选项主要面向“未标记 external”的场景。在 monorepo 中,正确标记external(见 input-options.ts 的ExternalOption类型)可以从根源上避免依赖包路径被写入产物,而preserveModulesRoot则是在无法标记时的兜底手段。
  4. 善用input的对象形式与命名:当需要精确控制每个产物的输出名时,应优先使用对象形式input: { name: 'path' }配合entryFileNames等命名模板,而不是依赖路径推导。

六、小结

output.preserveModulesRoot是 Rolldown preserve modules 模式下控制产物目录结构的精准“修剪器”:它以一行配置剥离输入模块的公共目录前缀,让输出目录结构在输入目录变化时保持稳定,尤其适合第三方模块未标记external与 monorepo 多包开发的场景。其实现位于 Rust 生成阶段的 chunk 命名流程中,包含“优先剥离前缀、失败回退到入口基目录”的健壮逻辑,并有针对 slash 规范化模块 id 的跨平台测试用例作为保障。理解这一选项,有助于你在使用 Rolldown 时产出目录结构干净、可预期且与 Rollup 行为一致的打包产物。

【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 17:20:55

Pwndbg contextunwatch 命令详解:从 context 中移除监视表达式

Pwndbg contextunwatch 命令详解:从 context 中移除监视表达式 【免费下载链接】pwndbg Exploit Development and Reverse Engineering with GDB & LLDB Made Easy 项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg 本篇文章围绕 pwndbg 的 con…

作者头像 李华
网站建设 2026/9/15 17:20:21

Loop:免费 Mac 窗口管理,一个键摆好屏幕

Loop:免费 Mac 窗口管理,一个键摆好屏幕 【免费下载链接】Loop Window management made elegant. 项目地址: https://gitcode.com/GitHub_Trending/lo/Loop 光标贴在窗口右下角,你已经第 N 次去够那条 1 像素的边——拖快了&#xff0…

作者头像 李华
网站建设 2026/9/15 17:19:44

Zend Engine(Zend引擎)运行机制

一句话总纲:Zend Engine 就是PHP的虚拟机。把PHP源码编译成Zend中间字节码(OPcode),然后在虚拟机里逐条解释执行OPcode;负责内存管理、变量处理、函数调用、对象、异常,连接扩展,最终调用操作系…

作者头像 李华
网站建设 2026/9/15 17:18:31

F´ 数据结构的基石:ArraySetOrMapImpl 外部存储集合实现详解

F 数据结构的基石:ArraySetOrMapImpl 外部存储集合实现详解 【免费下载链接】fprime F - A flight software and embedded systems framework 项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime ArraySetOrMapImpl 是 F(F Prime&#xf…

作者头像 李华
网站建设 2026/9/15 17:18:09

从剑侠情缘源码到地图编辑器:经典RPG游戏开发技术复盘

我当年第一次打开这份《剑侠情缘》整套源码的时候,说实话心里挺复杂的。一方面是对国产RPG里程碑作品的好奇,另一方面又带着审视的眼光——97年的代码,放到今天还能读出什么东西来?结果从阅读源码到把地图编辑器真正跑起来&#x…

作者头像 李华
网站建设 2026/9/15 17:18:07

EDF Browser:生物信号处理的零代码瑞士军刀

1. EDF Browser到底是什么?一个被低估的生物信号处理“瑞士军刀”EDF Browser不是什么花哨的新概念,它是我过去八年在神经电生理、睡眠研究和临床脑电图分析中用得最多、最稳、也最容易被新手忽略的桌面工具。很多人第一次听说它,是因为实验室…

作者头像 李华