> ## Documentation Index
> Fetch the complete documentation index at: https://opc-43d279b8.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 模块化

> CommonJS 与 ESM 的加载机制、live binding、循环依赖、Tree Shaking 以及 exports/import.meta 的用法。

> 模块这块我踩过循环依赖：拿到的是半成品，报错位置还很迷惑。后来才补上条件导出和 `import.meta` 的用途。

***

## Q1: 模块化的历史？为什么 JavaScript 需要模块化？

```text theme={null}
┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│  无模块时代   │ → │  社区方案    │ → │  语言标准    │ → │  构建工具    │
│  2005-2009   │ │  2009-2013   │ │  2015+       │ │  2020+       │
│  script 标签 │ │  AMD/CMD     │ │  ES Modules  │ │  原生 ESM    │
└──────────────┘   └──────────────┘   └──────────────┘   └──────────────┘
```

**1. 2005 年: 脚本地狱（Script Hell）**

早期的 JavaScript 没有模块系统。所有代码都通过 `<script>` 标签加载，全局变量互相覆盖，加载顺序必须手动管理。一个中等规模的项目可能有 50 多个 `<script>` 标签，顺序一改就报错。

**2. 2009 年: CommonJS 与 AMD 诞生**

Ryan Dahl 创建 Node.js，带来了 CommonJS 规范——用 `require()` 导入、`module.exports` 导出。同一时期，浏览器端诞生了 AMD（Asynchronous Module Definition）规范，RequireJS 是它的实现，用 `define()` 定义模块。两个规范分别统治了服务端和浏览器端。

**3. 2015 年: ES Modules 成为语言标准**

ES6 正式引入了 `import` / `export` 语法，JavaScript 终于有了语言层面的模块标准。ES Modules 支持静态分析，让 Tree Shaking 成为可能——这是构建工具性能优化的基础。

**模块化解决的核心问题：**

```text theme={null}
问题：全局变量污染 + 依赖管理混乱

<script src="jquery.js"></script>     <!-- jQuery 定义 $ -->
<script src="prototype.js"></script>   <!-- Prototype 也定义 $ -->
<script src="app.js"></script>         <!-- $ 被覆盖了，代码崩溃 -->

顺序还得手动管理——jquery 必须在 app.js 前面

解决：每个模块有自己的作用域，显式声明依赖

import $ from 'jquery';      // 显式导入，不污染全局
import { ajax } from './api'; // 依赖关系一目了然
```

> [!NOTE]
>
> 模块化让 JavaScript 从"脚本语言"进化为"工程语言"——每个文件是一个独立的模块，有自己的作用域和明确的依赖声明。

> [card] 没有模块化
>
> 一家公司没有部门划分，所有员工都在一个大开间里。张三的文件和李四的文件混在一起，谁都能改别人的文件。新来的员工要花两周搞清楚"这些文件的先后顺序是什么"。一旦有人改了共享文件，全公司的工作都可能受影响。

> [card] 引入模块化
>
> 有了部门（模块）之后，每个部门有自己的办公室（作用域），通过正式的申请流程（import/export）与其他部门交互。新员工入职时只需要了解自己部门的接口，不需要知道其他部门的内部实现。

> [!TIP]
>
> 面试时先讲"为什么需要模块化"（全局污染、依赖管理），再讲"怎么实现的"（CommonJS vs ES Modules），最后讲"Tree Shaking 和构建优化"。展示你从历史到原理到工程实践的完整理解。

***

## Q2: CommonJS —— Node.js 的模块系统

CommonJS 是 Node.js 默认的模块系统。它的核心特点是：**同步加载、运行时解析、值的拷贝。**

```text theme={null}
┌──────────────┐         ┌──────────────┐
│  a.js        │ require │  b.js        │
│              │ ──────→ │              │
│  const b =   │         │  module.     │
│  require(b)  │ ←────── │  exports = {}│
│              │ 返回值  │              │
└──────────────┘         └──────────────┘
```

```javascript theme={null}
// b.js — CommonJS 导出
const name = "module-b";
function greet() {
  return `Hello from ${name}`;
}

// module.exports 是模块对外暴露的接口——require 拿到的就是这个对象
module.exports = { name, greet };
// 注意：exports 是 module.exports 的引用，直接给 exports 赋值会断开引用
// 所以推荐直接操作 module.exports

// a.js — CommonJS 导入
const b = require("./b"); // 同步加载 b.js，执行其代码，返回 module.exports
console.log(b.name); // 'module-b'      // 通过返回的 exports 对象访问导出值
console.log(b.greet()); // 'Hello from module-b'
```

### CommonJS 的加载机制

```text theme={null}
require() 的执行过程：

1. 解析路径 → 找到文件
2. 检查缓存 → 如果已加载，直接返回缓存的 exports
3. 如果没缓存 → 创建一个空的 module 对象
4. 执行模块代码 → 填充 module.exports
5. 缓存 module → 返回 module.exports
```

```javascript theme={null}
/**
 * CommonJS 的缓存机制：每个文件只执行一次，后续 require 直接返回缓存
 * 这就是为什么 CommonJS 模块是"单例"的
 */
// a.js
console.log("a 被加载了");
const b1 = require("./b"); // 第一次 require：加载并执行 b.js，缓存 module.exports
const b2 = require("./b"); // 第二次 require：直接返回缓存，不重新执行 b.js
console.log(b1 === b2); // true           // 同一个引用——证明模块只执行了一次

// b.js 只会被执行一次，输出一次 'a 被加载了'
```

### CommonJS 的值拷贝特性

> [!WARNING]
>
> 这是最容易出错的地方。CommonJS 在 `require` 时会执行模块代码，然后返回 `module.exports` 对象本身。但由于导出时 `{ count, increment }` 里的 `count` 只是当时值的快照，后续模块内部的 `count++` 不会回写到这个对象属性上。
>
> ```javascript theme={null}
> /**
>  * CommonJS 的值拷贝陷阱
>  * require 时执行模块代码，返回 module.exports 对象
>  * 基本类型（如 count=0）在导出时就被拷贝进对象，后续修改不影响已拷贝的值
>  */
>
> // counter.js
> let count = 0;
> function increment() {
>   count++;
> } // 内部修改 count（从 0 变为 1）
> module.exports = { count, increment }; // 导出时 count 的值是 0，被拷贝到 exports
>
> // main.js
> const { count, increment } = require("./counter");
> console.log(count); // 0                  // 拿到的是 require 时 count 的值（拷贝）
> increment(); // 调用 increment，counter.js 内部的 count 变为 1
> console.log(count); // 0                  // 但 main.js 中的 count 仍然是拷贝时的值 0！
>
> // 注意：即使是对象引用，count 是基本类型也会被拷贝
> const counter = require("./counter");
> counter.increment();
> console.log(counter.count); // 0          // counter.count 也是拷贝
> // 如果想拿到最新值，需要重新设计导出方式（如用 getter 函数）
> ```

### CommonJS 的循环依赖处理

```javascript theme={null}
/**
 * CommonJS 循环依赖的执行过程
 * 核心：遇到循环 require 时，返回的是当前已执行部分的 exports（半成品）
 * 这是 Node.js 处理循环依赖的机制——不会死循环，但拿到的值可能不完整
 */

// a.js
console.log("a 开始");
exports.done = false; // 设置 a 的初始状态
const b = require("./b"); // 遇到 require('./b') → 暂停 a，去执行 b.js
console.log("在 a 中, b.done =", b.done); // b 已经执行完毕，b.done = true
exports.done = true; // 更新 a 的状态
console.log("a 结束");

// b.js
console.log("b 开始");
exports.done = false; // 设置 b 的初始状态
const a = require("./a"); // 此时 a.js 还在执行中，Node.js 返回 a 当前的 exports
console.log("在 b 中, a.done =", a.done); // false — 拿到的是 a 执行到一半的"半成品"！
exports.done = true;
console.log("b 结束");
// 执行顺序：a 开始 → b 开始 → 在 b 中, a.done = false → b 结束 → 在 a 中, b.done = true → a 结束
```

> [!TIP]
>
> CommonJS 的 `require()` 就像打一通同步电话——你打电话给同事要一份文件（require），同事必须立刻找到文件发给你，你拿到文件后才能继续工作。如果同事也需要你的文件（循环依赖），你们就陷入了"你等我、我等你"的尴尬——最终先拿到的是对方"还没做完的半成品"。

***

## Q3: ES Modules —— JavaScript 的语言标准

ES Modules（ESM）是 ES6 引入的官方模块系统。它的核心特点是：**静态分析、异步加载、值的引用（live bindings）。**

```text theme={null}
┌──────────────┐         ┌──────────────┐
│  a.mjs       │ import  │  b.mjs       │
│              │ ──────→ │              │
│  import {    │         │  export const│
│    name      │ ←────── │  name = 'b'; │
│  } from './b'│ live    │              │
│              │ binding │              │
└──────────────┘         └──────────────┘
```

```javascript theme={null}
// b.mjs — ES Modules 导出
// export 声明必须在模块顶层（不能在 if/for/函数内），这是静态分析的前提
export const name = "module-b"; // 命名导出：导出变量的绑定（live binding）
export function greet() {
  return `Hello from ${name}`;
}
// 也可以统一导出：export { name, greet };

// a.mjs — ES Modules 导入
import { name, greet } from "./b.js"; // 按名称导入，名字必须匹配
console.log(name); // 'module-b'
console.log(greet()); // 'Hello from module-b'

// 重命名导入：用 as 关键字起别名
import { name as moduleName } from "./b.js";

// 默认导出：每个模块只能有一个 default export
// b.js: export default function() {}
// a.js: import myFunc from './b.js';  // 默认导出的名称可以自定义
```

### ES Modules 的静态分析能力

ES Modules 的 `import` / `export` 必须在模块顶层，不能在条件语句或函数内使用。这不是限制，而是为了让打包工具在**编译时**就能分析出模块的依赖关系：

```javascript theme={null}
// ❌ 不能在条件中使用 import
if (condition) {
  import { foo } from "./foo.js"; // SyntaxError
}

// ✅ 动态导入用 import()
const module = await import("./foo.js");

// ✅ 静态导入必须在顶层
import { foo } from "./foo.js";
```

```text theme={null}
ES Modules 的静态分析能力带来的好处：

1. Tree Shaking：编译时就能知道哪些 export 没有被 import，打包时删除未使用的代码
2. 代码分割：import() 动态导入可以实现按需加载
3. 类型检查：TypeScript 可以在编译时检查模块的类型
```

### ES Modules 的 Live Bindings（活绑定）

> [!WARNING]
>
> ES Modules 的导出是 **live binding**——导入方拿到的是对导出变量的引用，而不是值的拷贝。当模块内部修改了导出变量，导入方会看到最新值。
>
> ```javascript theme={null}
> /**
>  * ESM 的 live binding（活绑定）示例
>  * import 拿到的是变量的引用，不是值的拷贝
>  * 模块内部修改变量后，import 方通过 live binding 能看到最新值
>  */
>
> // counter.mjs
> export let count = 0; // export 导出的是变量的绑定
> export function increment() {
>   count++;
> } // 内部修改 count，外部通过 live binding 感知
>
> // main.mjs
> import { count, increment } from "./counter.mjs";
> console.log(count); // 0                  // 初始值
> increment(); // 调用 increment，counter.mjs 内部 count 变为 1
> console.log(count); // 1                  // 通过 live binding 拿到最新值！
>
> // 但注意：import 绑定是只读的（read-only view）
> // count = 10; // SyntaxError: "count" is read-only
> // 要修改导出值，只能通过调用导出的函数（如 increment）
> ```

### CommonJS vs ES Modules 对比

| 对比维度 | CommonJS | ES Modules |
| - | - | - |
| **语法** | `require()` / `module.exports` | `import` / `export` |
| **加载方式** | 同步加载 | 异步加载 |
| **解析时机** | 运行时解析 | 编译时静态分析 |
| **导出值** | 值的拷贝（导出时快照） | 值的引用（live binding） |
| **this 指向** | `module.exports` 对象 | `undefined` |
| **循环依赖** | 返回不完整的 exports | 通过 live binding 获取最新值 |
| **Tree Shaking** | 不支持 | 支持 |
| **环境** | Node.js 默认 | 浏览器 + Node.js（.mjs 或 type: "module"） |

***

## Q4: 循环依赖 —— 模块化的经典陷阱

循环依赖是模块化中最容易出 bug 的场景：模块 A 依赖模块 B，模块 B 又依赖模块 A。

```text theme={null}
┌──────────┐         ┌──────────┐
│  模块 A  │ ──────→ │  模块 B  │
│          │ ←────── │          │
└──────────┘         └──────────┘
      ↑                    │
      └────────────────────┘
           循环依赖
```

### CommonJS 如何处理循环依赖

```javascript theme={null}
// a.js
console.log("a 开始加载");
const b = require("./b");
console.log("a 中 b.done =", b.done); // 可能是不完整的值
exports.done = true;
console.log("a 加载完成");

// b.js
console.log("b 开始加载");
const a = require("./a");
console.log("b 中 a.done =", a.done); // false — a 还没执行完
exports.done = true;
console.log("b 加载完成");
```

```text theme={null}
CommonJS 的处理机制：

1. 执行 a.js → 遇到 require('./b') → 暂停 a.js，去执行 b.js
2. 执行 b.js → 遇到 require('./a') → a.js 正在执行中
3. Node.js 返回 a.js 当前已执行部分的 exports（不完整的值）
4. b.js 继续执行完毕
5. 回到 a.js 继续执行

结果：b.js 拿到的是 a.js 的"半成品"
```

### ES Modules 如何处理循环依赖

```javascript theme={null}
/**
 * ESM 循环依赖的处理：通过 live binding 机制
 * 即使变量还没被赋值，绑定关系已在实例化阶段建立
 * 求值阶段赋值后，import 方通过 live binding 能拿到最新值
 */

// a.mjs
import { bDone } from "./b.mjs"; // 导入 b 的 bDone 变量（live binding）
console.log("a 中 bDone =", bDone); // 如果 a 先执行，bDone 可能还是 undefined
export const aDone = true; // 导出 aDone

// b.mjs
import { aDone } from "./a.mjs"; // 导入 a 的 aDone 变量（live binding）
console.log("b 中 aDone =", aDone); // 如果 b 先执行，aDone 可能还是 undefined
export const bDone = true; // 导出 bDone
```

```text theme={null}
ES Modules 的处理机制：

1. 解析阶段：静态分析所有 import/export，建立模块依赖图
2. 实例化阶段：为每个模块创建 Module Record，分配内存空间
3. 求值阶段：按照依赖顺序执行模块代码

ES Modules 的 live binding 机制让循环依赖更优雅：
- 即使变量还没被赋值，绑定关系已经建立了
- 等变量被赋值后，导入方能拿到最新值
```

### 如何避免循环依赖

```javascript theme={null}
// ❌ 循环依赖：A 和 B 直接互相引用
// service-a.js
import { processB } from "./service-b.js";
export function processA() {
  return processB() + 1;
}

// service-b.js
import { processA } from "./service-a.js";
export function processB() {
  return processA() + 1;
}

// ✅ 解决方案 1：提取公共模块
// shared.js
export function baseProcess() {
  return 1;
}

// service-a.js
import { baseProcess } from "./shared.js";
export function processA() {
  return baseProcess() + 1;
}

// service-b.js
import { baseProcess } from "./shared.js";
export function processB() {
  return baseProcess() + 2;
}

// ✅ 解决方案 2：依赖注入
// service-a.js
export function createServiceA(serviceB) {
  return { processA: () => serviceB.processB() + 1 };
}
```

> [!WARNING]
>
> 大多数循环依赖说明模块的职责划分有问题。如果 A 和 B 互相依赖，通常意味着它们应该合并成一个模块，或者把共享的部分提取到第三个模块。不要试图"解决"循环依赖，而应该**消除**它。

***

## Q5: Tree Shaking —— ES Modules 的杀手级特性

Tree Shaking 是 ES Modules 带来的最重要的构建优化：**在打包时删除没有被使用的导出代码。** 它的名字来源于"摇树"——摇动树干，枯叶自然掉落。

```text theme={null}
┌──────────────────────────┐
│  源代码                   │
│  export function used() {}│  ← 被 import 了
│  export function unused(){}│  ← 没有被 import
└──────────────┬───────────┘
               │ Tree Shaking
               ▼
┌──────────────────────────┐
│  打包结果                 │
│  function used() {}       │  ← 保留
│                          │
│  unused() 被删除了        │
└──────────────────────────┘
```

### Tree Shaking 的工作原理

```text theme={null}
Tree Shaking 的实现步骤：

1. 构建依赖图：静态分析所有 import/export 语句
2. 标记（Mark）：从入口文件开始，标记所有被使用的 export
3. 清除（Sweep）：删除没有被标记的 export
4. 压缩（Minify）：删除死代码（dead code elimination）
```

### 什么情况下 Tree Shaking 会失效

```javascript theme={null}
// ❌ 情况 1：CommonJS 模块不能 Tree Shaking
const utils = require("./utils"); // 动态导入，编译时无法分析
utils.used();

// ❌ 情况 2：副作用（Side Effects）模块
// 如果模块有副作用，打包工具不敢删除它
import "./polyfill.js"; // 这个文件可能修改了全局对象

// ✅ 在 package.json 中声明无副作用
// { "sideEffects": false }
// 或者指定哪些文件有副作用：
// { "sideEffects": ["*.css", "./src/polyfill.js"] }

// ❌ 情况 3：导出被重新赋值
export let count = 0; // let 导出可能被修改，打包工具保守处理
export const COUNT = 0; // const 导出不会被修改，更安全
```

### Webpack 的 Tree Shaking 配置

```javascript theme={null}
// webpack.config.js
module.exports = {
  mode: "production", // 自动开启 Tree Shaking
  optimization: {
    usedExports: true, // 标记未使用的导出
    minimize: true, // 删除死代码
    sideEffects: true, // 识别并跳过无副作用的模块
  },
};

// 确保使用 ES Modules 语法
import { debounce } from "lodash-es"; // ✅ 可以 Tree Shaking
import _ from "lodash"; // ❌ 无法 Tree Shaking（CommonJS）
```

> [!TIP]
>
> Tree Shaking 就像自助餐的按需取用。你走进餐厅（打包入口），只拿了你吃的菜（import 的模块）。厨房（打包工具）发现你没拿"红烧肉"（未使用的导出），就把红烧肉从菜单上划掉了（Tree Shaking）。但如果你的盘子里放了一个"可能影响其他菜味道"的调料瓶（副作用模块），厨房就不敢随便划掉它。

***

## Q6: 动态导入（Dynamic Import）—— 按需加载

动态导入用 `import()` 函数实现运行时的模块加载，返回一个 Promise。它是代码分割和懒加载的基础。

```text theme={null}
┌──────────────┐         ┌──────────────┐
│  主包         │ import() │  异步 Chunk  │
│  (首页代码)   │ ──────→ │  (详情页代码) │
│              │ 返回     │              │
│              │ ←────── │              │
│              │ Promise │              │
└──────────────┘         └──────────────┘

用户访问首页 → 只加载主包
用户点击"查看详情" → 动态加载详情页代码
```

```javascript theme={null}
// 静态导入：打包时就确定依赖关系
import HeavyComponent from "./HeavyComponent";

// 动态导入：运行时按需加载
button.addEventListener("click", async () => {
  const { default: HeavyComponent } = await import("./HeavyComponent");
  render(HeavyComponent);
});
```

### React 中的懒加载

```javascript theme={null}
import { lazy, Suspense } from "react";

// 路由级别的代码分割
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}
```

### Webpack 的代码分割

```javascript theme={null}
// Webpack 会自动把 import() 的模块分割成独立的 Chunk
// 输出：
// main.js          — 主包（首页代码）
// Dashboard.js     — 异步 Chunk（详情页代码）
// Settings.js      — 异步 Chunk（设置页代码）

// 魔法注释：给 Chunk 命名
const Dashboard = lazy(
  () =>
    import(
      /* webpackChunkName: "dashboard" */
      "./pages/Dashboard"
    ),
);
```

### 动态导入 vs 静态导入的选择

| 场景 | 推荐方式 | 原因 |
| - | - | - |
| 核心功能（首屏） | 静态 import | 立即需要，减少请求 |
| 路由页面 | 动态 import() | 按需加载，减少首屏体积 |
| 大型库（如 ECharts） | 动态 import() | 用户可能不触发图表功能 |
| 条件性功能 | 动态 import() | 只在满足条件时加载 |

***

## Q7: 模块化在构建工具中的实现

### Webpack 的模块化实现

Webpack 把所有模块（包括 CommonJS 和 ES Modules）统一转换为自己的运行时格式：

```javascript theme={null}
/**
 * Webpack 打包后的运行时代码（简化版）
 * Webpack 将所有模块包装在一个 IIFE 中，用自己的 require 实现模块加载
 * 核心机制：模块缓存 + 包装函数 + 依赖注入
 */
(function (modules) {
  var installedModules = {}; // 模块缓存：已加载的模块不会重复执行

  /**
   * Webpack 版的 require：模拟 CommonJS 的 require 行为
   * @param {string} moduleId - 模块路径（如 './src/utils.js'）
   * @returns {object} 模块的 exports 对象
   */
  function __webpack_require__(moduleId) {
    if (installedModules[moduleId]) {
      // 检查缓存：已加载则直接返回
      return installedModules[moduleId].exports;
    }
    // 创建新模块对象：i=模块ID, l=是否已加载, exports=导出对象
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}, // 空的 exports 对象，模块代码执行后填充
    });
    // 执行模块代码：用 call 绑定 this 为 module.exports
    // 传入 module, exports, __webpack_require__ 三个参数
    // 这就是为什么 CommonJS 模块中能直接用 module.exports 和 require()
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __webpack_require__,
    );
    module.l = true; // 标记为已加载
    return module.exports; // 返回填充后的 exports
  }

  // 加载入口模块，启动整个应用
  return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
  // 模块表：每个模块路径对应一个包装函数（Webpack 将源码包装在这里）
  "./src/index.js": function (module, exports, __webpack_require__) {
    var _utils = __webpack_require__("./src/utils.js"); // 同步 require，加载 utils 模块
    console.log(_utils.add(1, 2));
  },
  "./src/utils.js": function (module, exports) {
    exports.add = function (a, b) {
      return a + b;
    }; // 挂载到 exports 上
  },
});
```

### Vite 的模块化实现

Vite 和 Webpack 的核心区别：**Webpack 先打包再启动开发服务器，Vite 利用浏览器原生 ESM 直接启动。**

```text theme={null}
Webpack 开发模式：
  1. 分析所有依赖
  2. 把所有模块打包成 Bundle
  3. 启动开发服务器
  4. 修改文件 → 重新打包 → 刷新

Vite 开发模式：
  1. 启动开发服务器（几乎瞬间）
  2. 浏览器请求模块时，按需转换和返回
  3. 修改文件 → 只重新编译修改的模块 → HMR
```

```javascript theme={null}
// Vite 开发模式下，浏览器直接请求 ESM 模块
// http://localhost:3000/src/App.tsx → Vite 实时转换为浏览器可执行的 ESM
// import { ref } from 'vue' → 转换为 '/node_modules/vue/dist/vue.esm-bundler.js'

// Vite 的预构建（Pre-bundling）：
// 用 esbuild 把 CommonJS 的 node_modules 转为 ESM
// 解决了"裸模块导入"（bare import）的问题
```

### Webpack vs Vite 对比

| 对比维度 | Webpack | Vite |
| - | - | - |
| **开发启动** | 慢（先打包） | 快（原生 ESM） |
| **HMR** | 中等（重新编译模块链） | 快（精确更新单个模块） |
| **生产构建** | 成熟稳定 | 基于 Rollup，同样稳定 |
| **生态插件** | 极其丰富 | 快速成长 |
| **适用场景** | 复杂项目、需要高度定制 | 新项目、追求开发体验 |

***

## Q8: 模块化的最佳实践

### 文件组织结构

```text theme={null}
src/
├── components/          # 通用组件
│   ├── Button/
│   │   ├── index.ts     # 导出入口
│   │   ├── Button.tsx   # 组件实现
│   │   └── Button.test.tsx
├── pages/               # 页面组件（路由级别代码分割）
│   ├── Dashboard/
│   └── Settings/
├── hooks/               # 自定义 Hooks
├── utils/               # 工具函数（纯函数，容易 Tree Shaking）
├── services/            # API 服务
├── stores/              # 状态管理
└── types/               # TypeScript 类型定义
```

### Barrel Export 模式

```javascript theme={null}
// utils/index.ts — Barrel Export
export { debounce } from "./debounce";
export { throttle } from "./throttle";
export { formatDate } from "./formatDate";

// 使用时：一个 import 搞定
import { debounce, throttle } from "@/utils";

// ⚠️ 注意：Barrel Export 可能影响 Tree Shaking
// 如果 barrel 文件导出了大量模块，打包工具可能无法精确 Tree Shaking
// 解决方案：直接导入具体文件
import { debounce } from "@/utils/debounce";
```

### 模块边界（Module Boundary）

```javascript theme={null}
// ❌ 直接导入其他模块的内部文件
import { helper } from "../utils/internal/helper";

// ✅ 通过模块的公共 API 导入
import { helper } from "../utils";

// 模块应该对外暴露稳定的公共 API，隐藏内部实现
// 这样内部重构不会影响其他模块
```

> [!WARNING]
>
> 1. **依赖方向单一化**：上层模块可以依赖下层模块，下层不能依赖上层
> 2. **提取公共模块**：如果 A 和 B 都需要的功能，提取到 C
> 3. **依赖注入**：通过参数传入依赖，而不是直接 import
> 4. **Lint 规则**：用 `eslint-plugin-import` 的 `no-cycle` 规则自动检测循环依赖
>
> ```javascript theme={null}
> // .eslintrc.js
> rules: {
>   'import/no-cycle': 'error' // 编译时报错循环依赖
> }
> ```

***

## 底层源码：Node.js 和浏览器如何加载模块？

Node.js 的 require() 是怎么实现的？

Node.js 的模块加载器在 `lib/internal/modules/cjs/loader.js` 中实现：

```javascript theme={null}
/**
 * Node.js require() 的核心逻辑（简化版，源自 lib/internal/modules/cjs/loader.js）
 * 完整流程：路径解析 → 缓存检查 → 原生模块检查 → 创建模块 → 编译执行 → 返回 exports
 */
Module._load = function (request, parent, isMain) {
  // 第 1 步：解析文件路径——将 './b' 转为绝对路径（如 /project/src/b.js）
  var filename = Module._resolveFilename(request, parent);

  // 第 2 步：检查模块缓存——同一个文件只执行一次，后续 require 直接返回缓存
  // 这就是为什么 CommonJS 模块是"单例"的
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;
  }

  // 第 3 步：检查是否为原生模块（如 fs、path、http 等）
  var NativeModule = require("internal/bootstrap/loaders");
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }

  // 第 4 步：创建新模块对象，存入缓存
  var module = new Module(filename, parent);
  Module._cache[filename] = module; // 先存缓存——这是处理循环依赖的关键

  // 第 5 步：根据文件扩展名选择加载方式（.js、.json、.node）
  tryModuleLoad(module, filename);

  // 第 6 步：返回 module.exports——此时模块代码已经执行完毕
  return module.exports;
};

/**
 * 编译模块代码：将源码包装在函数中并执行
 * 这就是 CommonJS 中 module、exports、require、__filename、__dirname 的来源
 * @param {string} content - 模块源码
 * @param {string} filename - 文件路径
 */
Module.prototype._compile = function (content, filename) {
  // Module.wrap 将源码包装在一个函数中，注入 5 个参数
  // 包装后的代码：(function(exports, require, module, __filename, __dirname) { <源码> })
  var wrapper = Module.wrap(content);
  // vm.runInThisContext 在当前 V8 上下文中编译并返回函数
  var compiledWrapper = vm.runInThisContext(wrapper);
  // 用 call 调用包装函数，传入 module.exports、require 等参数
  var result = compiledWrapper.call(
    this.exports,
    this.exports,
    require,
    this,
    filename,
    dirname,
  );
  return result;
};
```

这段代码揭示了 Node.js 模块的几个关键机制：

1. **模块缓存**：`Module._cache` 用文件路径作为 key，确保每个文件只执行一次
2. **包装函数**：每个模块代码被包装在一个函数中，`exports`、`require`、`module` 作为参数传入
3. **路径解析**：`Module._resolveFilename` 负责解析 `require()` 的参数为绝对路径

浏览器原生 ESM 是怎么加载的？

浏览器的 ESM 加载分三个阶段：

```javascript theme={null}
/**
 * 浏览器原生 ESM 加载的三个阶段
 * 与 Node.js 的 CommonJS 不同，浏览器 ESM 是异步加载的
 */

// 阶段 1：构建（Construction）
// 浏览器解析 HTML 中的 <script type="module"> 标签
// 递归下载所有 import 依赖的模块文件（通过 HTTP 请求）
// 建立完整的模块依赖图（Module Graph）——只解析，不执行代码

// 阶段 2：实例化（Instantiation）
// 为每个模块分配内存空间（Module Record）
// 将 import 和 export 的变量"链接"起来（建立 live binding）
// 不执行任何代码，只是建立绑定关系——此时变量值为 undefined

// 阶段 3：求值（Evaluation）
// 按依赖图的拓扑排序顺序执行模块代码
// 此时变量才被赋值，获得实际值

// 浏览器加载 ESM 的方式：
<script type="module" src="./app.mjs"></script>
// type="module" 属性告诉浏览器：
// 1. 自动 defer（延迟执行，不阻塞 HTML 解析——与普通 script 的阻塞行为不同）
// 2. 自动 strict mode（ESM 强制严格模式，不用写 'use strict'）
// 3. 每个模块有自己的作用域（不像普通 script 共享全局作用域）
// 4. 支持 import/export 语法
```

***

## 十、及格回答 vs 满分回答

### 问题："CommonJS 和 ES Modules 有什么区别？"

**及格回答（60-70分）：**

> "CommonJS 用 require 导入，ES Modules 用 import 导入。CommonJS 是同步的，ES Modules 是异步的。CommonJS 在 Node.js 用，ES Modules 在浏览器用。"

问题出在：**知道表面区别，不理解底层机制**。

**满分回答（90-100分）：**

> CommonJS 和 ES Modules 最本质的区别是**静态分析能力**。ES Modules 的 `import` / `export` 必须在模块顶层，不能在条件语句中使用，这让打包工具在编译时就能构建完整的依赖图——这是 Tree Shaking 的基础。CommonJS 的 `require()` 是一个普通函数调用，可以在任何地方执行，编译时无法确定依赖关系。<sup>\[1]</sup>

> 第二个关键区别是导出值的行为。CommonJS 的 `require()` 返回的是 `module.exports` 对象本身，但导出那一刻基本类型的值已经作为属性存进对象，模块内部后续对变量的重新赋值不会回写这个属性，所以观察起来像"值的拷贝"。ES Modules 导出的是 live binding——导入方拿到的是对导出变量的引用，模块内部修改变量后，导入方能看到最新值。这意味着 CommonJS 的 `require()` 在执行时就已经确定了值，而 ES Modules 的值在求值阶段才确定。<sup>\[2]</sup>

> 第三个区别是循环依赖的处理。CommonJS 遇到循环依赖时，返回的是"当前已执行部分的 exports"——一个不完整的对象。ES Modules 通过 live binding 机制，即使变量还没被赋值，绑定关系已经建立，等赋值完成后导入方能拿到最新值。但两种方式在实践中都是"设计问题的信号"，应该通过提取公共模块或依赖注入来消除循环依赖。<sup>\[3]</sup>

> 在实际项目中，Node.js 从 12.17 / 13.2 起正式稳定支持 ESM（`.mjs` 扩展名或 `package.json` 的 `"type": "module"`），现代前端项目统一使用 ESM。开发时用 Vite 享受原生 ESM 的快速启动，生产构建用 Webpack/Rollup 的 Tree Shaking 优化体积。

### 常见追问

> [accordion] require 和 import 的执行时机有什么区别？
>
> **答题要点：**
> require 是运行时同步执行，import 是编译时静态解析。
>
> **高分回答：**
> "`require()` 是一个普通的 JavaScript 函数调用，在运行时同步执行。当代码执行到 `const a = require('./a')` 时，Node.js 会暂停当前模块的执行，去加载并执行模块 a，拿到返回值后继续执行。
>
> `import` 语句在编译阶段就被解析了。引擎在执行任何代码之前，就已经知道了所有的依赖关系。`import` 语句会被提升到模块顶部（类似变量提升），实际的值绑定在求值阶段完成。
>
> ```javascript theme={null}
> // CommonJS：运行时执行
> if (condition) {
>   const a = require('./a'); // 条件满足才加载
> }
>
> // ES Modules：编译时解析，import 被提升
> import a from './a'; // 不管在哪里写，都会在编译时被解析
> // ❌ 不能在条件中使用 import
> ```

> [accordion] 什么是 Tree Shaking？它的前提条件是什么？
>
> **答题要点：**
> Tree Shaking 依赖 ES Modules 的静态分析能力。
>
> **高分回答：**
> "Tree Shaking 是在打包时删除未使用代码的优化技术。它的工作原理是：
>
> 1. 构建模块依赖图（利用 ESM 的静态 import/export）
> 2. 标记所有被引用的导出
> 3. 删除未被标记的导出
>
> 前提条件：
>
> 1. **使用 ES Modules 语法**：CommonJS 的 `require()` 是动态的，无法静态分析
> 2. **声明 sideEffects**：在 `package.json` 中设置 `"sideEffects": false`，告诉打包工具这些模块没有副作用，可以安全删除
> 3. **使用 const 导出**：`export const` 比 `export let` 更容易被 Tree Shaking（不会被重新赋值）
> 4. **避免 Barrel Export 的陷阱**：如果 barrel 文件中有副作用代码，可能影响 Tree Shaking"

> [accordion] Node.js 中怎么使用 ES Modules？
>
> **答题要点：**
> 两种方式：.mjs 扩展名或 package.json 的 type: "module"。
>
> **高分回答：**
> "Node.js 从 12.17 / 13.2 起正式稳定支持 ESM（12.0 到 12.16 需要 `--experimental-modules`），有两种启用方式：
>
> 1. 文件扩展名用 `.mjs`
> 2. 在 `package.json` 中设置 `\"type\": \"module\"`
>
> 注意事项：
>
> * ESM 中不能使用 `require()`、`module`、`exports`、`__dirname`、`__filename`
> * 需要替代方案：`import.meta.url` 是当前模块的文件 URL，可替代 `__filename`；`path.dirname(fileURLToPath(import.meta.url))` 等价于 `__dirname`（Node 20.11+ 也可直接用 `import.meta.dirname`）
> * CommonJS 和 ESM 可以互相导入，但有兼容性限制
>
> ```javascript theme={null}
> // ESM 中导入 CommonJS 模块
> import cjsModule from './commonjs-module.js';
>
> // CommonJS 中导入 ESM 模块（必须用动态 import）
> const esmModule = await import('./esm-module.mjs');
> ```

> [accordion] 循环依赖在 CommonJS 和 ESM 中分别会怎样？
>
> **答题要点：**
> CommonJS 返回不完整的 exports，ESM 通过 live binding 获取最新值。
>
> **高分回答：**
> "**CommonJS**：遇到循环依赖时，返回的是"当前已执行部分的 `module.exports`"。如果模块 A 执行到一半就去 require 模块 B，而 B 又 require A，B 拿到的是 A 的"半成品"。
>
> **ES Modules**：通过 live binding 机制处理。在实例化阶段就建立了 import 和 export 的绑定关系，即使变量还没被赋值。求值阶段按依赖顺序执行，赋值完成后导入方通过 live binding 能拿到最新值。
>
> 但两种方式在实际项目中都不可靠。最好的做法是**消除循环依赖**：提取公共模块、使用依赖注入、或者重新设计模块职责。"

> [accordion] Vite 为什么比 Webpack 快？
>
> **答题要点：**
> Vite 利用浏览器原生 ESM，不需要在开发时打包。
>
> **高分回答：**
> "Vite 在开发模式下比 Webpack 快的根本原因是**不需要打包**。
>
> Webpack 的开发模式流程：分析依赖 → 打包所有模块到一个 Bundle → 启动开发服务器 → 文件修改 → 重新打包 → 刷新。项目越大，启动越慢。
>
> Vite 的开发模式流程：启动开发服务器（几乎瞬间） → 浏览器请求哪个模块，实时转换哪个模块返回。利用浏览器原生 ESM 的 `import` 语句，每个 `import` 都是一个 HTTP 请求，Vite 拦截这些请求，用 esbuild 实时转换。
>
> 对于 node\_modules 中的 CommonJS 模块，Vite 用 esbuild 做**预构建**（Pre-bundling），一次性转为 ESM 并缓存。
>
> 生产构建 Vite 用 Rollup，同样支持 Tree Shaking 和代码分割。"

> [accordion] 什么是 Module Federation？它解决了什么问题？
>
> **答题要点：**
> Module Federation 让多个独立构建的应用在运行时共享模块。
>
> **高分回答：**
> "Module Federation 是 Webpack 5 引入的特性，它允许**多个独立构建的应用在运行时共享模块**。
>
> 它解决的核心问题是：微前端场景下，多个子应用如何共享依赖（如 React、lodash），避免重复加载。
>
> ```javascript theme={null}
> // webpack.config.js — 应用 A（提供模块）
> new ModuleFederationPlugin({
>   name: 'appA',
>   filename: 'remoteEntry.js',
>   exposes: {
>     './Button': './src/components/Button',
>   },
>   shared: ['react', 'react-dom'],
> });
>
> // 应用 B（消费模块）
> new ModuleFederationPlugin({
>   name: 'appB',
>   remotes: {
>     appA: 'appA@http://localhost:3001/remoteEntry.js',
>   },
>   shared: ['react', 'react-dom'],
> });
>
> // 应用 B 的代码中
> const Button = React.lazy(() => import('appA/Button'));
> ```
>
> 关键特性：
>
> 1. **运行时加载**：不需要提前打包，运行时动态加载远程模块
> 2. **依赖共享**：shared 配置确保 React 等公共依赖只加载一次
> 3. **版本协商**：如果两个应用用的 React 版本不同，Webpack 会加载两个版本"

> [accordion] 如何设计一个支持 Tree Shaking 的工具库？
>
> **答题要点：**
> 从源码到构建到 package.json 都需要配合。
>
> **高分回答：**
> "设计一个支持 Tree Shaking 的工具库，需要从三个层面配合：
>
> **1. 源码层面**：
>
> * 每个函数单独导出，不要把所有函数放在一个大对象里
> * 使用 `export const` 而不是 `export let`
> * 避免在模块顶层执行副作用代码
>
> ```javascript theme={null}
> // ❌ 不支持 Tree Shaking
> export default {
>   debounce: (fn, ms) => { /* ... */ },
>   throttle: (fn, ms) => { /* ... */ },
> };
>
> // ✅ 支持 Tree Shaking
> export const debounce = (fn, ms) => { /* ... */ };
> export const throttle = (fn, ms) => { /* ... */ };
> ```
>
> **2. 构建层面**：
>
> * 输出 ESM 格式（`"module": "dist/index.esm.js"`）
> * 用 Rollup 构建（Tree Shaking 能力最强）
> * 不要压缩和混淆开发版本（方便调试和 Tree Shaking）
>
> **3. package.json 层面**：
>
> ```json theme={null}
> {
>   "main": "dist/index.cjs.js",
>   "module": "dist/index.esm.js",
>   "sideEffects": false,
>   "exports": {
>     ".": {
>       "import": "./dist/index.esm.js",
>       "require": "./dist/index.cjs.js"
>     },
>     "./debounce": "./dist/debounce.js"
>   }
> }
> ```
>
> `exports` 字段让打包工具可以直接导入子路径（`import debounce from 'my-lib/debounce'`），进一步提升 Tree Shaking 效果。条件按书写顺序匹配，先命中 `import` 或 `require`，最后用 `"default"` 兜底；`"types"` 条件供 TypeScript 解析类型。"

> [accordion] ESM 和 CommonJS 在 Node.js 中混用有什么坑？
>
> **答题要点：**
> 双包问题（Dual Package Hazard）和导入方式的兼容性限制。
>
> **高分回答：**
> "ESM 和 CommonJS 在 Node.js 中混用最大的坑是**双包问题**（Dual Package Hazard）：
>
> 如果一个库同时提供了 CJS 和 ESM 两个版本，当主应用用 ESM 导入，而某个依赖用 CJS 导入同一个库时，Node.js 会加载两个实例——它们各自有不同的内部状态，导致 `instanceof` 检查失败、单例模式失效。
>
> ```javascript theme={null}
> // 库同时提供两个版本
> // "exports": { "import": "./esm/index.js", "require": "./cjs/index.js" }
>
> // 主应用（ESM）
> import { Store } from 'my-lib';
> const s = new Store();
>
> // 某个依赖（CJS）
> const { Store } = require('my-lib');
> s instanceof Store; // false — 两个不同的 Store 类！
> ```
>
> 解决方案：
>
> 1. 只发布 ESM 版本（需 Node 12.17+）
> 2. 用 Conditional Exports 但确保内部状态用全局单例
> 3. 在 `package.json` 中用 `"type": "module"` 统一为 ESM
>
> 另一个常见坑：ESM 中不能用 `require()`，如果需要动态导入，必须用 `await import()`。CJS 中导入 ESM 必须用 `await import()`（需要在 async 上下文中）。"

***

## Q9: ESModule 的工作原理是什么？

ES Modules 不仅仅是"用 `import`/`export` 替代 `require`/`module.exports`"。它的本质是一套**在编译时静态分析依赖、在求值阶段通过 live binding 建立引用关系**的模块系统。理解 ESM 的工作原理，是理解 Tree Shaking、代码分割、Vite 快速启动的前提。

```text theme={null}
ES Modules 的三阶段加载模型：

┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│   构建        │ → │   实例化      │ → │   求值        │
│ Construction  │   │ Instantiation│   │ Evaluation   │
│               │   │              │   │              │
│ 解析 import   │   │ 分配内存空间  │   │ 执行模块代码  │
│ 递归下载依赖  │   │ 建立绑定关系  │   │ 变量获得值    │
│ 不执行代码    │   │ 不执行代码    │   │              │
└──────────────┘   └──────────────┘   └──────────────┘
```

### 阶段 1：构建（Construction）

引擎从入口文件开始，递归解析所有 `import` 语句，构建完整的模块依赖图（Module Graph）。这个阶段**只做静态分析，不执行任何代码**。

```javascript theme={null}
/**
 * 阶段 1：构建（Construction）
 * 引擎从入口文件开始，递归解析所有 import 语句，构建模块依赖图
 * 这个阶段只做静态分析，不执行任何代码——这就是 ES Modules 能做 Tree Shaking 的原因
 */

// main.mjs — 入口文件
import { count } from "./counter.mjs"; // 引擎在此发现依赖 counter.mjs
import { debounce } from "./utils.mjs"; // 引擎在此发现依赖 utils.mjs

// counter.mjs — 被依赖的模块
export let count = 0; // export 声明被引擎在构建阶段记录
export function increment() {
  count++;
}

// 构建阶段完成后的依赖图（Module Graph）：
// main.mjs → [counter.mjs, utils.mjs]   main 依赖两个模块
// counter.mjs → []                       counter 无依赖
// utils.mjs → []                         utils 无依赖
// 注意：此时变量 count 的值还没有被赋值，代码还没有执行
```

### 阶段 2：实例化（Instantiation）

引擎为每个模块创建一个 **Module Record**（模块记录），为所有 `export` 变量分配内存空间（此时变量未赋值，值为 `undefined`），然后将 `import` 和 `export` 的变量"链接"起来——这就是 **live binding**。

```javascript theme={null}
/**
 * 阶段 2：实例化（Instantiation）
 * 为每个 export 变量分配内存空间，建立 import/export 之间的 live binding
 * 此阶段不执行代码，变量值为 undefined，但绑定关系已建立
 */

// 实例化后的内存布局：
// counter.mjs 的 export count ←→ main.mjs 的 import count（指向同一块内存）
// 即使 count 还没被赋值（值为 undefined），绑定关系已经建立
// 当 count 在求值阶段被赋值为 0 时，main.mjs 通过 live binding 自动看到 0
// 这就是 ESM 和 CommonJS 的本质区别：ESM 是引用绑定，CommonJS 是值拷贝
```

### 阶段 3：求值（Evaluation）

按照依赖顺序（拓扑排序）执行模块代码，变量在此时获得实际值。由于 live binding 已经建立，模块间可以实时看到彼此的最新值。

```javascript theme={null}
/**
 * 阶段 3：求值（Evaluation）
 * 按依赖顺序（拓扑排序）执行模块代码，变量在此时获得实际值
 * 由于 live binding 已在实例化阶段建立，模块间可以实时看到彼此的最新值
 */

// 求值顺序（按依赖图的拓扑排序）：
// 1. counter.mjs 先执行：count 被赋值为 0，increment 被赋值为函数
// 2. main.mjs 后执行：通过 live binding 拿到 count = 0

// live binding 的威力：
// counter.mjs 中执行 increment() 后，count 变为 1
// main.mjs 中读取 count 会得到 1——因为 import 拿到的是引用，不是值的拷贝
// 如果是 CommonJS，main.js 中的 count 永远是 0（值在 require 时就固定了）
```

### Live Binding vs 值拷贝

这是 ESM 与 CommonJS 最本质的区别：

```javascript theme={null}
/**
 * Live Binding vs 值拷贝：ESM 与 CommonJS 最本质的区别
 * ESM 的 export 是活绑定——import 拿到的是变量的引用，模块内部修改后 import 方能看到最新值
 * CommonJS 的 require 是值拷贝——require 时执行模块代码，返回 exports 的浅拷贝
 */

// === ESM 的 live binding（活绑定）===
// counter.mjs
export let count = 0; // export 导出的是变量的绑定，不是值
export function increment() {
  count++;
} // 内部修改 count，外部通过 live binding 自动感知

// main.mjs
import { count, increment } from "./counter.mjs";
console.log(count); // 0                  // 初始值
increment(); // 调用 increment，count 在 counter.mjs 内部变为 1
console.log(count); // 1                  // 通过 live binding 拿到最新值！

// === CommonJS 的值拷贝 ===
// counter.js
let count = 0;
function increment() {
  count++;
}
module.exports = { count, increment }; // 导出时 count 的值 0 被拷贝到 exports 对象

// main.js
const { count, increment } = require("./counter");
console.log(count); // 0                  // require 时拿到的是值的拷贝
increment(); // 调用 increment，counter.js 内部的 count 变为 1
console.log(count); // 0                  // 但 main.js 中的 count 仍然是拷贝时的值 0
```

> [!WARNING]
>
> 虽然 ESM 的导入是 live binding（引用），但**导入方不能直接修改导入的变量**。这是规范的硬性要求——`import` 绑定是只读的（read-only view）。要修改导出值，只能通过调用导出的函数。
>
> ```javascript theme={null}
> import { count, increment } from "./counter.mjs";
> // count = 10;  // SyntaxError: "count" is read-only
> // import 绑定是只读的——这是规范的硬性要求
> // 防止导入方意外修改导出方的状态，保证单向数据流
> increment(); // ✅ 通过导出的函数间接修改——这是修改导出值的唯一方式
> ```

### ESM 与 Tree Shaking 的关系

ESM 的静态分析能力是 Tree Shaking 的基础。因为 `import`/`export` 必须在模块顶层（不能在条件语句或函数内），打包工具可以在编译时精确知道哪些导出被使用了：

```javascript theme={null}
// 编译时就能知道：debounce 被 import 了，throttle 没有
import { debounce } from "./utils.mjs";

// utils.mjs
export const debounce = (fn, ms) => {
  /* ... */
};
export const throttle = (fn, ms) => {
  /* ... */
};
// throttle 没有被任何模块 import → 打包时删除
```

CommonJS 的 `require()` 是普通函数调用，可以在任何地方执行，编译时无法确定依赖关系，所以无法 Tree Shaking。

> [!TIP]
>
> CommonJS 就像现场施工——工人到了现场才知道要什么材料（运行时解析），缺什么临时去拿。ES Modules 就像先画好完整的设计图（编译时静态分析），所有的材料清单（依赖关系）在开工前就确定了。有了设计图，就能精确知道哪些材料会被用到（Tree Shaking），哪些是多余的可以直接划掉。

***

## 延伸阅读与引用参考

* [Node.js 官方文档: Modules](https://nodejs.org/api/modules.html)：Node.js 模块系统的权威文档，包含 CommonJS 的加载机制和缓存策略。<sup>\[1]</sup>
* [MDN: JavaScript Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)：ES Modules 的官方教程，包含浏览器端和 Node.js 端的使用方式。<sup>\[2]</sup>
* [Vite 官方文档: Why Vite](https://vitejs.dev/guide/why.html)：Vite 作者尤雨溪对"为什么需要新一代构建工具"的深度解释，理解原生 ESM 的价值。<sup>\[3]</sup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.