前端构建产物里的 __esModule 是什么?一次讲清楚它的原理和作用

如果你经常翻构建后的代码,基本都会看到这样一行:

js 复制代码
Object.defineProperty(exports, "__esModule", { value: true });

很多人第一次看到都会疑惑:

  • 这是干嘛的?
  • 能删吗?
  • 不加会怎么样?
  • 和 default 导出有什么关系?

这篇文章专门把这个现象讲清楚。


太长不看版

js 复制代码
Object.defineProperty(exports, "__esModule", { value: true });

本质就是:

标记"这个 CommonJS 文件是从 ES Module 转译来的",用于默认导出语义的互操作。

它不是功能代码,不是业务逻辑。

它只是模块系统演化过程中的一个兼容标志。

一、为什么会出现 __esModule?

根本原因只有一个:

ES Module 和 CommonJS 的语义不一样。

我们简单对比一下。

ES Module

javascript 复制代码
export default function foo() {}

CommonJS

java 复制代码
module.exports = function foo() {}

两者看起来都叫"默认导出",但内部机制完全不同。

当构建工具(TypeScript / Babel / Webpack / Rspack 等)把 ESM 转成 CJS 时,语义必须"模拟"出来。

于是就变成:

java 复制代码
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = foo;

关键问题来了:

如何区分"普通 CJS 模块"和"从 ESM 转过来的 CJS 模块"?

这就是 __esModule 存在的意义。


二、__esModule 到底做了什么?

它只是一个标记。

ini 复制代码
exports.__esModule = true

之所以用 Object.defineProperty,是为了:

  • 不可枚举
  • 更符合 Babel 的标准输出
  • 避免污染遍历结果

本质就是:

告诉别人:这个模块原本是 ES Module。

仅此而已。


三、真正的核心:默认导出的互操作问题

来看一个经典场景。

1️⃣ 原始 ESM

js 复制代码
export default function foo() {}

2️⃣ 被编译成 CJS

js 复制代码
exports.default = foo;

3️⃣ 用 CommonJS 引入

js 复制代码
const foo = require('./foo');

得到的其实是:

sql 复制代码
{
  default: [Function: foo]
}

这就有问题了。

我们希望的是:

js 复制代码
foo() // 直接调用

而不是:

js 复制代码
foo.default()

于是构建工具会生成一个 helper:

js 复制代码
function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

逻辑是:

  • 如果模块带有 __esModule 标记 → 说明是 ESM 转的 → 直接用 default
  • 如果没有 → 说明是普通 CJS → 包一层 { default: obj }

这就是整个互操作的关键。


四、为什么不能只判断 default 属性?

因为普通 CJS 也可能写:

js 复制代码
module.exports = {
  default: something
}

这时你没法区分:

  • 是 ESM 编译产物
  • 还是普通对象刚好有个 default 字段

所以必须有一个"官方标记"。

__esModule 就成了事实标准。


五、什么时候会生成它?

只要发生:

ESM → CJS 转译

基本都会生成。

常见场景:

  • TypeScript 编译为 module: commonjs
  • Babel preset-env 输出 CJS
  • Webpack / Rspack 输出 target 为 node + CJS
  • 老 Node 项目混用 import / require

如果你使用:

json 复制代码
{
  "type": "module"
}

并且输出原生 ESM

那就不会有 __esModule。

它只存在于"模块系统过渡时代"。


注意:它不是 JS 语言特性

非常重要的一点:

__esModule 不是语言规范的一部分。

它是:

  • Babel 约定
  • 构建器约定
  • 社区事实标准

是一种"工程层解决方案"。

换句话说:

它属于模块系统演化历史的一部分。

从更高层看:模块系统的过渡遗产

JavaScript 的模块系统经历了三代:

  1. 无模块(全局变量时代)
  2. CommonJS(Node 时代)
  3. ES Module(标准化)

但 Node 生态已经建立在 CJS 上。

所以必须有一个桥接层。

__esModule 就是这座桥的一块砖。

它存在的原因不是设计优雅,而是历史兼容。

相关推荐
软件技术NINI4 分钟前
圣罗兰彩妆html+css 4页
前端·css·html
这个DBA有点耶19 分钟前
ELT 工具选型完整指南:从 ETL 差异分析到工程落地
数据库·架构·代码规范
2601_9516152034 分钟前
网站模板 网站网页模板下载
前端·网络·html
酷炫码神1 小时前
Day 1|HTML5:一个商城首页的骨架,从 DOCTYPE 到语义化
前端·html·html5
147API1 小时前
AWS Priming 怎样用层级蒸馏,把 Transformer 改造成混合架构
深度学习·架构·transformer
AI源码2 小时前
鲜花商城(HTML 版)
前端·html
hiahiahia1232 小时前
Message 和对话
服务器·前端·数据库
loulanyue_2 小时前
芯片设计:从最底层讲起——读MatX CEO Reiner Pope激辩逻辑门、脉动阵列与GPU架构演进
架构
张彦峰ZYF2 小时前
一次触发、一把锁与一个号码:分布式基础组件的统一设计推演
架构·先立六条时间纪律·再拆解编号的位分配·节点编号分配与回拨四策·辨析互斥、租约与领导权三种语·调度拆成七个子问题·五层架构
码上上班2 小时前
基于 K3s 的微服务容器化与 GitOps 持续交付平台
微服务·云原生·架构