ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Rolldown 的 `output.esModule` 配置与 `__esModule` 标记跨工具互操作指南

Rolldown 的 `output.esModule` 配置与 `__esModule` 标记跨工具互操作指南 Rolldown 的output.esModule配置与__esModule标记跨工具互操作指南【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown导读__esModule是 JavaScript 打包生态中一个广为流传但从未被任何运行时标准化的合成属性转译工具用它标记这份 CommonJS 代码原本是 ES Module消费方则依赖它决定default导入的取值方式。本文以 Rolldown 的output.esModule配置为切入点完整讲解该选项三个取值true/if-default-prop/false的行为差异、默认值设计动机以及产物被 Rolldown、esbuild、Node.js、Babel 等不同工具消费时的互操作差异并结合仓库源码与测试用例说明 Rolldown 在运行时层面的实际处理逻辑。读完本文你将能够为自己的库正确配置output.esModule并理解__esModule在不同消费场景下的行为边界。output.esModule是什么在 Rolldown 的选项体系中output.esModule属于输出层选项OutputOptions其 TypeScript 定义位于 packages/rolldown/src/options/output-options.ts/** * Whether to add a __esModule: true property when generating exports for non-ES formats. * * This property signifies that the exported value is the namespace of an ES module * and that the default export of this module corresponds to the .default property * of the exported object. * * - true: Always add the property when using named exports mode, which is similar to what other tools do. * - if-default-prop: Only add the property when using named exports mode and there also is a default export. * - false: Never add the property even if the default export would become a property .default. * * default if-default-prop */ esModule?: boolean | if-default-prop;一句话概括该选项决定在 CJSCommonJS与 IIFE 这两种非 ES 输出格式中是否向产物注入一个合成的__esModule: true属性。它只对非 ES 格式生效——es/esm格式本身就是标准 ES Module无需也不应添加该标记。选项的语义与默认值取值行为适用场景true只要处于 named exports 模式就总是注入Object.defineProperty(exports, __esModule, { value: true })兼容 Babel 等以__esModule为约定的转译/打包工具if-default-prop仅当处于 named exports 模式且模块确实存在 default 导出时才注入库作者推荐选择无 default 导出时CJS 消费者把全部命名导出整体作为 default 拿到而非得到undefined或报错false即使 default 导出会变成.default属性也绝不注入面向不识别__esModule的运行环境如 Node.js发布三个取值与boolean之间还存在等价关系源码 crates/rolldown_common/src/inner_bundler_options/types/es_module_flag.rs 中的Frombool实现将true映射为Always、false映射为Neverif-default-prop字符串则映射为IfDefaultProp因此你在配置里写esModule: true等价于总是注入写false等价于永不注入。为什么默认值是if-default-propRolldown 默认采用if-default-prop源码中的EsModuleFlag::IfDefaultProp标注为#[default]其设计理由记录在 es_module_flag.rs 的注释中核心观点是__esModule属性不是任何 JavaScript 运行时遵循的标准会引发大量互操作问题因此要把它的使用限制在真正需要的场景。Rolldown 对if-default-prop的具体判定逻辑对于如下入口代码——存在default导出export default function() {}且还有命名导出a模块被自动识别为named导出模式因此会注入__esModuleexport default function() {} export const a 1; // 该模块会被自动视为 named 导出模式对于如下入口代码——只有命名导出、没有default导出则不会注入__esModuleexport const a 1;__esModule标记的跨工具消费差异注入__esModule只是手段最终目的是让谁消费这个产物能拿到符合预期的导出。不同工具对__esModule的处理方式并不一致这正是 packages/rolldown/src/options/docs/output-es-module.md 的核心内容消费方对__esModule的态度default导入结果Rolldown采用基于 Node.js 行为的启发式规则视导入方模块类型与__esModule标志综合判定esbuild采用基于 Node.js 行为的启发式规则与 Rolldown 类似的启发式判定Node.js不识别__esModuledefault 就是module.exports的完整值Babel识别__esModule带__esModule时 default 取.default属性这一表格揭示了一个关键事实__esModule是工具间约定而非运行时标准。Rolldown 与 esbuild 选择模仿 Node.js 的行为做启发式判断Node.js 自身完全不看这个属性Babel 则把它当作权威标志。Rolldown 与 Node.js 的启发式Rolldown 对default导入 CJS 模块的启发式规则详见 docs/in-depth/bundling-cjs.md。只要命中以下任一条件default导入取导入方importeeCJS 模块的module.exports整体值否则取module.exports.default导入方是.mjs或.mts文件动态导入时导入方是.cjs或.cts文件导入方最近的package.json将type字段设为module动态导入时导入方最近的package.json将type字段设为commonjs导入方 CJS 模块的module.exports.__esModule不为true导入方 CJS 模块的module.exports没有自有owndefault属性。其中最后一条专门处理只设置__esModule却没有真正提供default导出的模块例如 tslib 的 UMD 构建没有这一条回退default导入将变成undefined。rollup/plugin-commonjs也是用同样的回退逻辑处理该情况的。运行时层面的印证__toESM与__toCommonJSRolldown 运行时辅助函数 crates/rolldown/src/runtime/runtime-base.js 中__toESM和__toCommonJS直接体现了对__esModule的检查export var __toESM (mod, isNodeMode, target) ( (target mod ! null ? __create(__getProtoOf(mod)) : {}), __copyProps( // __esModule alone is not enough: the module must own a default (#10360). isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, default) ? __defProp(target, default, { value: mod, enumerable: true }) : target, mod, ) ); export var __toCommonJS (mod) __hasOwnProp.call(mod, module.exports) ? mod[module.exports] : __copyProps(__defProp({}, __esModule, { value: true }), mod);可以读出两层关键信息__toESM判断是否把整个mod当作 default时__esModule单独存在是不够的——注释明确指出模块必须自有一个default对应 issue #10360。这与上文启发式规则的最后一条完全一致只有__esModule为true且确实拥有自有default属性时才走.default取值路径。__toCommonJS在把 ES Module 转成 CJS 形态时会合成__esModule: true——这正是output.esModule注入行为在运行时层面的落点当输出格式为 CJS/IIFE 且配置要求注入时导出的对象上会出现__esModule。源码中的实现EsModuleFlag枚举Rolldown 在 Rust 侧将output.esModule建模为枚举 EsModuleFlag包含三个变体Always在 CJS 和 IIFE 格式中总是生成Object.defineProperty(exports, __esModule, { value: true })与多数打包器的行为一致Never在 CJS 和 IIFE 格式中绝不生成该合成属性IfDefaultProp默认仅在模块有 default 导出时生成。该枚举通过 serde 的rename_all kebab-case序列化因此配置层同时接受always、never、if-default-prop以及布尔值。deny_unknown_fields意味着传入枚举之外的字符串会在反序列化阶段直接报错。测试用例验证仓库在 crates/rolldown/tests/rolldown/function/es_module 下维护了一组针对该选项的测试覆盖三种取值 × CJS/IIFE 两种格式的组合always配置为esModule: always、format: iife即使入口只有命名导出也注入标记never配置为esModule: never无论是否 default 导出都不注入if_default_prop_cjs_true/if_default_prop_cjs_falseCJS 格式下有/无 default 导出两种情形的对比if_default_prop_iife_true/if_default_prop_iife_falseIIFE 格式下的同样对比array_destructuring_rest_bindingdefault 导出为数组解构 rest binding 时的边界行为。以always测试为例其 _config.json 为{ config: { exports: named, esModule: always, format: iife, name: module } }入口 main.js 只有命名导出export const validator if_default_prop_false; export const value true;由于配置为always即使没有default导出产物依然会注入__esModule: true——这正是true与if-default-prop之间最直观的行为分水岭。实践建议结合文档与源码为不同发布形态给出配置建议发布给 Node.js 直连消费的库优先esModule: false。Node.js 不识别__esModule其require()得到的就是module.exports整体不注入标记可以让行为更贴近运行时真实语义发布给 Babel /rollup/plugin-commonjs生态消费的库esModule: true能让这些识别__esModule的工具把default正确解析为.default属性通用库Rolldown 默认保持if-default-prop。有 default 导出时注入标记以便转译工具正确取.default没有 default 导出时不注入让 CJS 消费者把全部命名导出整体作为 default 拿到而不是得到undefined或报错面向 Rolldown / esbuild 自身再打包两者都采用基于 Node.js 行为的启发式会综合判断导入方扩展名、package.json的type字段与__esModule标志因此只要遵守 Node 的模块类型判定约定.mjs/.cjs或type字段两种打包器都能给出符合预期的一致性结果。最后提醒一个生态细节由于__esModule从未被标准化跨工具消费时天然存在默认导出取值不一致的兼容性风险。编写同时面向 Babel 派与 Node.js 派的兼容代码时可以采用显式双重判断来消除歧义例如import rawFoo from ./importee.cjs; const foo typeof rawFoo object rawFoo ! null rawFoo.__esModule ? rawFoo.default : rawFoo;该写法在两种解释下都输出一致结果。若怀疑某个依赖包存在此类不兼容可借助 publint 的cjs_with_esmodule_default_export规则做初步排查该规则只抽查包内部分文件问题定位到具体依赖后再考虑通过补丁方式规避。延伸阅读Bundling CJS 指南Rolldown 对 CommonJS 模块的一等支持以及default导入启发式的完整推导过程非 ESM 输出格式CJS、IIFE、UMD 等格式的转换细节Rolldown 选项参考output.esModule及其他输出层选项的完整 TypeScript 定义【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表