基于 Rolldown 的 output.format 全解析:ES、CommonJS、IIFE 与 UMD 的选型与生成原理
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
Rolldown 是一款基于 Rust 实现、API 与 Rollup 兼容的 JavaScript/TypeScript 打包器。output.format是决定产物以何种模块规范呈现的核心配置项,直接关系到产物在浏览器、Node.js 与各类构建工具之间的可用性。本文以仓库中的 output-format.md 为骨架,结合 JS 侧类型定义与 Rust 侧渲染实现,系统讲解es、cjs、iife、umd四种格式的生成形态、适用场景与配套选项,帮助你为每一个具体目标环境选出正确格式,并理解 Rolldown 在底层是如何把这些格式落地的。
一、output.format是什么
在 Rolldown 的配置中,format是 OutputOptions 上的一个可选属性,其类型定义为:
export type ModuleFormat = 'es' | 'cjs' | 'esm' | 'module' | 'commonjs' | 'iife' | 'umd';注意其中包含多组别名(output-options.ts):
'es'、'esm'、'module'是同一格式,均表示 ES Module;'cjs'、'commonjs'是同一格式,均表示 CommonJS Module;'iife'表示 Immediately Invoked Function Expression(立即执行函数表达式);'umd'表示 Universal Module Definition(通用模块定义)。
format的默认值是'es'。无论指定哪种别名,Rolldown 都会先将其归一化为内部格式InternalModuleFormat,即'es' | 'cjs' | 'iife' | 'umd'(见 normalized-output-options.ts)。
在 Rust 核心侧,格式被建模为OutputFormat枚举,四个变体Esm、Cjs、Iife、Umd与 JS 侧的InternalModuleFormat一一对应(见 output_format.rs)。该枚举提供了一系列辅助方法,在打包的不同阶段被反复查询:
is_esm()/is_esm_or_cjs():判断是否需要按 ESM(或 ESM/CJS)语义处理;keep_esm_import_export_syntax():仅在es格式下保留原生import/export语法;should_call_runtime_require():除cjs、umd、iife外,其余格式需要运行时require支持;source_type():将格式映射为 oxc 的SourceType——es对应.mjs,cjs对应.cjs,iife/umd则作为带 script 标志的 CJS 处理。
例如在模块最终化阶段(finalizer_context.rs),当import.meta无法被替换时,错误诊断信息中会带上options.format.as_str()来提示用户当前产物的格式。
二、ES Module(es):推荐的默认格式
生成形态
当设置output.format: 'es'时,产物会使用标准的export语法:
function exportedFunction() { /* ... */ } let exportedValue = '/* ... */'; export { exportedFunction, exportedValue };ES 模块(ESM)是 JavaScript 的官方模块标准。加载方式取决于运行环境:
- 浏览器:使用
<script type="module">; - Node.js:使用
.mjs扩展名,或在package.json中声明"type": "module"。
为什么推荐
ES modules 是绝大多数场景下的推荐格式,原因在于:
- 它们是 JavaScript 规范的一部分,在浏览器与 Node.js 中通用;
- 静态分析友好,能够支撑 tree-shaking 等优化(对应 Rust 侧
keep_esm_import_export_syntax()返回true,产物保留原生语法,便于下游继续优化); - 互操作性最好。
相关细节
在 output-options.ts 的 JSDoc 中明确说明'es'、'esm'、'module'三者等价。此外,Rolldown 在platform相关文档中强调其默认输出格式始终是'esm',不随平台切换(例如 esbuild 在 Node.js 下默认输出 CJS,而 Rolldown 不会这么做),详情见 platform.md。
三、CommonJS Module(cjs):面向传统 Node.js
生成形态
当设置output.format: 'cjs'时,产物会使用exports变量:
function exportedFunction() { /* ... */ } let exportedValue = '/* ... */'; exports.exportedFunction = exportedFunction; exports.exportedValue = exportedValue;Live Binding 语义保留
CommonJS 是 Node.js 在 ES modules 出现之前原生支持的模块格式。原文档特别指出:入口点的 ES 模块导出会被转换为exports上的 getter,以保留 live binding 语义——即导出值可以被导出方后续修改,导入方读到的始终是最新值。
这与 Rust 侧reference_needed_symbols.rs中针对OutputFormat::Cjs的专门分支处理相互印证:非 ESM 格式需要生成额外的互操作代码来衔接import/export语义(见 reference_needed_symbols.rs)。
适用场景
- 目标环境是不支持 ES modules 的旧版 Node.js;
- 需要与期望 CommonJS 的老旧包集成。
相关配套选项
output.format: 'cjs'常与以下选项配合使用:
dynamicImportInCjs:默认true,保留外部动态导入为import(...);设为false时改写为require(...),以兼容不支持动态import()的旧版 Node.js(见 output-options.ts);esModule:默认'if-default-prop',决定是否为非 ES 格式的产物添加__esModule: true标记,该标记表明导出对象是 ES 模块的 namespace,且默认导出对应.default属性(见 output-options.ts);externalLiveBindings:默认true,为外部导入生成 live binding 支持代码;设为false可生成更小的产物,但要求外部模块的导出不会变化(见 output-options.ts)。
四、IIFE:面向单<script>标签的即插即用产物
生成形态
IIFE 即“立即执行函数表达式”。当设置output.format: 'iife'时,产物会被包裹在一个 IIFE 中,假设同时设置了output.name: 'MyLibrary':
var MyLibrary = (function () { function exportedFunction() { /* ... */ } let exportedValue = '/* ... */'; return { exportedFunction, exportedValue }; })();解决的问题
当使用不带type="module"的<script>标签时,代码在全局作用域执行,不同脚本之间的变量可能互相冲突。IIFE 通过创建私有函数作用域封装所有内部变量,只暴露一个全局变量(例如 jQuery、_、React 这类库的挂载方式),从根本上避免了全局污染。
适用场景
- 需要“扔进任意页面就能跑”的 drop-in 脚本与小组件;
- 希望只暴露一个干净全局变量的库(如分析脚本、可嵌入小部件)。
实现层面
在 Rust 渲染管线中,iife与umd格式在生成代码时需要对每个模块增加一层缩进(因为产物整体被包裹在函数体中),见 render_chunk_to_assets.rs:
let needs_extra_indent = matches!( self.options.format, rolldown_common::OutputFormat::Iife | rolldown_common::OutputFormat::Umd );相关配套选项
name:指定umd/iife格式下承载导出的全局变量名(见 output-options.ts);globals:为umd/iife格式下被标记为 external 的导入提供id: variableName映射。例如:
export default defineConfig({ external: ['jquery'], output: { format: 'iife', name: 'MyBundle', globals: { jquery: '$', }, }, });对应输入import $ from 'jquery'会生成var MyBundle = (function ($) { /* ... */ })($);(见 output-options.ts);
extend:默认false。设为true时全局变量以global.name = global.name || {}形式定义(可叠加),否则以global.name = {}形式覆盖(见 output-options.ts)。
五、UMD:多环境通用的历史方案
生成形态
UMD(Universal Module Definition)是一种可跨多环境工作的模式:AMD(RequireJS)、CommonJS(Node.js)与浏览器全局变量。当设置output.format: 'umd'时,产物会被一段环境探测代码包裹,假设设置了output.name: 'MyLibrary':
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : ((global = typeof globalThis !== 'undefined' ? globalThis : global || self), factory((global.myBundle = {}))); })(this, function (exports) { function exportedFunction() { /* ... */ } let exportedValue = '/* ... */'; exports.exportedFunction = exportedFunction; exports.exportedValue = exportedValue; });这段包裹逻辑依次探测:
- 是否处于 CommonJS 环境(
typeof exports === 'object' && typeof module !== 'undefined'); - 是否处于 AMD 环境(
typeof define === 'function' && define.amd); - 否则回退到浏览器全局,将导出挂到
global.myBundle上。
现状与建议
原文档明确指出:UMD 在 ES modules 广泛支持之前很流行,因为它让一份构建产物在所有环境都能工作。但在今天,UMD 已基本没有必要:
- 所有现代浏览器与 Node.js 都支持 ES modules;
- 打包器会自动处理模块互操作;
- UMD 格式带来额外运行时开销(环境探测代码),且更难被静态分析。
对新项目,请直接使用es格式。这与 Rolldown 默认输出'esm'的设计是一致的。
六、格式选型速查与常见组合
| 目标环境 | 推荐格式 | 关键理由 |
|---|---|---|
| 现代浏览器(原生 ESM) | es | <script type="module">直接加载,tree-shaking 友好 |
| Node.js 库(双格式发布) | es+cjs | 分别发布 ESM/CJS 两套产物,覆盖全部 Node 版本 |
| 传统 Node.js(无 ESM 支持) | cjs | exports语法在老版本可直接require |
单<script>全局脚本 | iife | 私有作用域 + 单一全局变量 |
| 需要同时兼容 AMD/CJS/全局(旧场景) | umd | 一份产物多处可用,但有运行时开销 |
一个同时面向现代与旧环境的典型配置:
export default defineConfig({ input: 'src/index.js', output: [ { format: 'es', dir: 'dist/es', entryFileNames: '[name].mjs' }, { format: 'cjs', dir: 'dist/cjs', exports: 'named' }, ], });七、格式在打包流程中的位置
格式选择不是发生在“最后一刻”,而是贯穿 Rolldown 的链接(link)与生成(generate)阶段。从源码中可以看到格式对多个环节的影响:
- 导出创建(create_exports_for_ecma_modules.rs):
Esm格式与其他格式在导出处理上走不同分支; - 模块导出种类判定(determine_module_exports_kind.rs):
es格式的判定逻辑与iife/umd不同; - 跨 chunk 链接计算(compute_cross_chunk_links.rs):
cjs与esm分别有各自的特殊处理; - 执行顺序包装(order_wrapping.rs):非
esm格式下需要额外的包装逻辑; - 渲染缩进(render_chunk_to_assets.rs):
iife/umd的整体包裹决定了模块代码需要额外缩进。
理解这一点有助于排查问题:例如,同一个模块图在切换format后,chunk 划分与代码形态都可能发生变化,这是格式语义差异的必然结果,而非 bug。
总结
output.format是 Rolldown 输出配置中最基础也最关键的选项之一。es是默认值与绝大多数场景的首选;cjs服务于旧版 Node.js 与 CJS 生态;iife适合单<script>全局脚本;umd作为历史方案在新项目中应避免。在 Rolldown 内部,格式被归一化为OutputFormat枚举,并在链接、生成、渲染的各个阶段驱动不同的代码生成策略——这正是理解产物形态差异的钥匙。
【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考