如果你经常翻构建后的代码基本都会看到这样一行Object.defineProperty(exports, __esModule, { value: true });很多人第一次看到都会疑惑这是干嘛的能删吗不加会怎么样和 default 导出有什么关系这篇文章专门把这个现象讲清楚。太长不看版Object.defineProperty(exports, __esModule, { value: true });本质就是标记“这个CommonJS 文件是从 ES Module转译来的”用于默认导出语义的互操作。它不是功能代码不是业务逻辑。它只是模块系统演化过程中的一个兼容标志。一、为什么会出现__esModule根本原因只有一个ES Module 和 CommonJS 的语义不一样。我们简单对比一下。ES Moduleexport default function foo() {}CommonJSmodule.exports function foo() {}两者看起来都叫“默认导出”但内部机制完全不同。当构建工具TypeScript / Babel/Webpack /Rspack等把 ESM 转成 CJS 时语义必须“模拟”出来。于是就变成Object.defineProperty(exports, __esModule, { value: true }); exports.default foo;关键问题来了如何区分“普通 CJS 模块”和“从 ESM 转过来的 CJS 模块”这就是__esModule存在的意义。二、__esModule到底做了什么它只是一个标记。exports.__esModule true之所以用Object.defineProperty是为了不可枚举更符合 Babel 的标准输出避免污染遍历结果本质就是告诉别人这个模块原本是 ES Module。仅此而已。三、真正的核心默认导出的互操作问题来看一个经典场景。1️⃣ 原始 ESMexport default function foo() {}2️⃣ 被编译成 CJSexports.default foo;3️⃣ 用 CommonJS 引入const foo require(./foo);得到的其实是{ default: [Function: foo] }这就有问题了。我们希望的是foo() // 直接调用而不是foo.default()于是构建工具会生成一个 helperfunction _interopRequireDefault(obj) { return obj obj.__esModule ? obj : { default: obj }; }逻辑是如果模块带有__esModule标记 → 说明是 ESM 转的 → 直接用default如果没有 → 说明是普通 CJS → 包一层{ default: obj }这就是整个互操作的关键。四、为什么不能只判断default属性因为普通 CJS 也可能写module.exports { default: something }这时你没法区分是 ESM 编译产物还是普通对象刚好有个 default 字段所以必须有一个“官方标记”。__esModule就成了事实标准。五、什么时候会生成它只要发生ESM → CJS 转译基本都会生成。常见场景TypeScript 编译为module: commonjsBabel preset-env 输出 CJSWebpack / Rspack 输出 target 为 node CJS老 Node 项目混用 import / require如果你使用{ type: module }并且输出原生 ESM那就不会有__esModule。它只存在于“模块系统过渡时代”。注意它不是 JS 语言特性非常重要的一点__esModule不是语言规范的一部分。它是Babel 约定构建器约定社区事实标准是一种“工程层解决方案”。换句话说它属于模块系统演化历史的一部分。从更高层看模块系统的过渡遗产JavaScript 的模块系统经历了三代无模块全局变量时代CommonJSNode 时代ES Module标准化但 Node 生态已经建立在 CJS 上。所以必须有一个桥接层。__esModule就是这座桥的一块砖。它存在的原因不是设计优雅而是历史兼容。