> ## Documentation Index
> Fetch the complete documentation index at: https://bun.zhcndoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 模块解析

> Bun 如何解析模块以及处理 JavaScript 和 TypeScript 中的导入

JavaScript 中的模块解析是一个复杂的话题。

生态系统目前正处于从 CommonJS 模块向原生 ES 模块的多年过渡期。TypeScript 强制执行自己的一套关于导入扩展名的规则，这些规则与 ESM 不兼容。不同的构建工具通过各自不兼容的机制支持路径重映射。

Bun 旨在提供一个一致且可预测的模块解析系统，可开箱即用。不幸的是，它仍然相当复杂。

## 语法

考虑以下文件。

<CodeGroup>
  ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { hello } from "./hello";

  hello();
  ```

  ```ts hello.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
  export function hello() {
    console.log("Hello world!");
  }
  ```
</CodeGroup>

当我们运行 `index.ts` 时，它会打印 "Hello world!"。

```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
bun index.ts
Hello world!
```

在这种情况下，我们从 `./hello` 导入，这是一个没有扩展名的相对路径。**带扩展名的导入是可选的，但同样支持。** 为了解析此导入，Bun 会按顺序检查以下文件：

* `./hello.tsx`
* `./hello.jsx`
* `./hello.ts`
* `./hello.mjs`
* `./hello.js`
* `./hello.cjs`
* `./hello.json`
* `./hello/index.tsx`
* `./hello/index.jsx`
* `./hello/index.ts`
* `./hello/index.mjs`
* `./hello/index.js`
* `./hello/index.cjs`
* `./hello/index.json`

导入路径可以选择性地包含扩展名。如果有扩展名，Bun 将只检查具有该确切扩展名的文件。

```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { hello } from "./hello";
import { hello } from "./hello.ts"; // 这样写是可以的
```

如果你导入 `from "*.js{x}"`，Bun 会额外检查匹配的 `*.ts{x}` 文件，以兼容 TypeScript 的[ES 模块支持](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#new-file-extensions)。

```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { hello } from "./hello";
import { hello } from "./hello.ts"; // 这样写是可以的
import { hello } from "./hello.js"; // 这也可以
```

Bun 支持 ES 模块（`import`/`export` 语法）和 CommonJS 模块（`require()`/`module.exports`）。下面的 CommonJS 版本在 Bun 中也同样有效。

<CodeGroup>
  ```js index.js icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/javascript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=14ec2cbdf78f597f421d87adcffda9ac" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const { hello } = require("./hello");

  hello();
  ```

  ```js hello.js icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/javascript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=14ec2cbdf78f597f421d87adcffda9ac" theme={"theme":{"light":"github-light","dark":"dracula"}}
  function hello() {
    console.log("Hello world!");
  }

  exports.hello = hello;
  ```
</CodeGroup>

不过，建议新项目避免使用 CommonJS。

***

## 模块系统

Bun 原生支持 CommonJS 和 ES 模块。ES 模块是新项目推荐使用的模块格式，但 CommonJS 模块在 Node.js 生态中仍被广泛使用。

在 Bun 的 JavaScript 运行时中，`require` 可被 ES 模块和 CommonJS 模块调用。如果目标模块是 ES 模块，`require` 返回模块命名空间对象（等同于 `import * as`）。如果目标模块是 CommonJS 模块，`require` 返回 `module.exports` 对象（与 Node.js 一致）。

| 模块类型     | `require()`    | `import * as`                                          |
| -------- | -------------- | ------------------------------------------------------ |
| ES 模块    | 模块命名空间对象       | 模块命名空间对象                                               |
| CommonJS | module.exports | `default` 是 `module.exports`，`module.exports` 的键作为命名导出 |

### 使用 `require()`

你可以 `require()` 任何文件或包，甚至是 `.ts` 或 `.mjs` 文件。

```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
const { foo } = require("./foo"); // 扩展名可选
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");
```

<Accordion title="什么是 CommonJS 模块？">
  2016 年，ECMAScript 添加了对[ES 模块](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)的支持。ES 模块是 JavaScript 模块的标准。但数以百万计的 npm 包仍采用 CommonJS 模块。

  CommonJS 模块是使用 `module.exports` 导出值的模块。通常使用 `require` 来导入 CommonJS 模块。

  ```ts my-commonjs.cjs icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/javascript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=14ec2cbdf78f597f421d87adcffda9ac" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const stuff = require("./stuff");
  module.exports = { stuff };
  ```

  CommonJS 与 ES 模块之间的最大区别是 CommonJS 模块是同步的，而 ES 模块是异步的。还有其他区别：

  * ES 模块支持顶层 `await`，CommonJS 模块不支持。
  * ES 模块始终处于[严格模式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)，而 CommonJS 模块则不一定。
  * 浏览器不原生支持 CommonJS 模块，但通过 `<script type="module">` 方式原生支持 ES 模块。
  * CommonJS 模块不可静态分析，而 ES 模块只允许静态导入和导出。

  **CommonJS 模块：** 是 JavaScript 中一种模块系统。其关键特点是加载和执行是同步的。也就是说，当你导入 CommonJS 模块时，其代码会立即执行，且程序必须等待它执行完毕后才能继续。就像从头到尾读完一本书，不跳页。

  **ES 模块（ESM）：** 是另一种引入的模块系统。它的行为与 CommonJS 略有不同。ESM 中，静态导入（使用 `import`）是同步的，就像 CommonJS 一样。当你用常规的 `import` 语句导入 ESM 时，其模块代码会立即执行，程序会一步一步执行相应操作。类似于逐页阅读一本书。

  **动态导入：** 令人迷惑的是，ES 模块支持通过 `import()` 函数动态导入模块。这是“动态导入”，是异步的，不会阻塞主程序执行。它会在后台加载模块，同时主程序继续运行。模块准备好后即可使用。就像边读书边获取补充信息，不需要暂停阅读。

  **总结：**

  * CommonJS 模块和静态 ES 模块 (`import` 语句) 工作方式类似，都是同步的，像连贯读完一本书。
  * ES 模块还支持用动态 `import()` 异步导入模块。这就像在阅读过程中查找额外信息而不中止。
</Accordion>

### 使用 `import`

你可以 `import` 任何文件或包，甚至是 `.cjs` 文件。

```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { foo } from "./foo"; // 扩展名可选
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";
```

### 同时使用 `import` 和 `require()`

在 Bun 中，你可以在同一文件内同时使用 `import` 和 `require` —— 它们都随时有效。

```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";

const myStuff = require("./my-commonjs.cjs");
```

### 顶层 await

唯一的例外是顶层 await。你不能 `require()` 使用了顶层 await 的文件，因为 `require()` 是同步函数。

幸好很少有库用顶层 await，所以这很少成为问题。但如果你的应用代码用到了顶层 await，确保该文件没有被项目中其他地方 `require()`。建议用 `import` 或[动态 `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import)。

***

## 导入包

Bun 实现了 Node.js 的模块解析算法，因此可以用裸模块名从 `node_modules` 中导入包。

```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { stuff } from "foo";
```

该算法的完整规范在 [Node.js 文档](https://nodejs.org/api/modules.html) 中有官方说明，这里不再赘述。简要地说：如果你导入 `from "foo"`，Bun 会向上遍历文件系统，寻找包含 `foo` 包的 `node_modules` 目录。

### NODE\_PATH

Bun 支持 `NODE_PATH` 用于额外的模块解析目录：

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_PATH=./packages bun run src/index.js
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";
```

多个路径用平台的分隔符分隔（Unix 是 `:`，Windows 是 `;`）：

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows
```

找到 `foo` 包后，Bun 会读取 `package.json` 来确定包的导入方式。为确定包的入口，Bun 首先读取 `exports` 字段并检查以下条件：

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node": "./index.js",
    "require": "./index.js", // 如果导入者是 CommonJS
    "import": "./index.mjs", // 如果导入者是 ES 模块
    "default": "./index.js"
  }
}
```

`package.json` 中首先匹配的这个条件将决定包的入口。

Bun 支持子路径的 [`"exports"`](https://nodejs.org/api/packages.html#subpath-exports) 和 [`"imports"`](https://nodejs.org/api/packages.html#imports)。

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}
```

子路径导入和条件导入可以共同使用。

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}
```

与 Node.js 一样，若在 `"exports"` 映射中指定了任何子路径，则禁止导入未明确导出的其他子路径；只能导入明确导出的文件。以上述 `package.json` 为例：

```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import stuff from "foo"; // 这样可行
import stuff from "foo/index.mjs"; // 这样不行
```

<Note>
  **发布 TypeScript** — 请注意 Bun 支持特殊的 `"bun"` 导出条件。如果你的库是用 TypeScript 编写的，可以直接将未转译的 TypeScript 文件发布到 `npm`。只需在包的 `"bun"` 条件中指定你的 `*.ts` 入口，Bun 将直接导入并执行你的 TypeScript 源文件。
</Note>

如果未定义 `exports`，Bun 会回退到先使用 `"module"`（仅用于 ESM 导入），然后是 [`"main"`](https://nodejs.org/api/packages.html#main)。

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}
```

### 自定义条件

`--conditions` 标志允许你指定一个条件列表，用于解析 `package.json` 的 `"exports"`。

此标志既支持 `bun build`，也支持 Bun 的运行时。

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# 使用 bun build：
bun build --conditions="react-server" --target=bun ./app/foo/route.js

# 使用 Bun 运行时：
bun --conditions="react-server" ./app/foo/route.js
```

你也可以在 `Bun.build` 中以编程方式使用 `conditions`：

```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});
```

***

## 路径重映射

Bun 支持通过 TypeScript 的 `tsconfig.json` 中的 [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) 进行导入路径重映射，这对编辑器支持良好。如果你不用 TypeScript，可以使用项目根目录下的 [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) 实现相同功能。

```json tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"],     // 映射标识符到文件
      "components/*": ["components/*"] // 通配符匹配
    }
  }
}
```

Bun 也支持 Node.js 风格的子路径导入，可在 `package.json` 中配置；映射路径必须以 `#` 开头。这种方法对编辑器支持不如前者，但两种方案可以同时使用。

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "imports": {
    "#config": "./config.ts",       // 映射标识符到文件
    "#components/*": "./components/*" // 通配符匹配
  }
}
```

<Accordion title="Bun 中 CommonJS 互操作的底层细节">
  Bun 的 JavaScript 运行时对 CommonJS 有原生支持。当 Bun 的 JavaScript 转译器检测到 `module.exports` 的用法时，会将文件视作 CommonJS。模块加载器会把转译后的模块包裹进如下形式的函数：

  ```js theme={"theme":{"light":"github-light","dark":"dracula"}}
  (function (module, exports, require) {
    // 转译后的模块代码
  })(module, exports, require);
  ```

  `module`、`exports` 和 `require` 十分类似于 Node.js 中的同名变量。它们通过 C++ 中的 [`with 作用域`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) 赋值。一个内部的 `Map` 用来存储 `exports` 对象，以处理模块未完全加载完前的循环 `require` 调用。

  当 CommonJS 模块成功评估后，会创建一个合成的模块记录（Synthetic Module Record），其 `default` ES 模块导出被设置为 `module.exports`，而 `module.exports` 对象中的键（如果是对象）会被重新导出为命名导出。

  使用 Bun 的打包器时，上述机制有所不同。打包器会将 CommonJS 模块包裹进 `require_${moduleName}` 函数，该函数返回 `module.exports` 对象。
</Accordion>

***

## `import.meta`

`import.meta` 对象允许模块访问有关自身的信息。它是 JavaScript 语言的一部分，但其内容未标准化。每个宿主环境（浏览器、运行时等）都可以自由实现各自的 `import.meta` 属性。

Bun 实现了如下属性。

```ts /path/to/project/file.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"

import.meta.main; // 如果此文件是由 `bun run` 直接执行，返回 `true`
 // 否则返回 `false`

import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
```

| 属性                     | 说明                                                                                                                                                                                                                                                                   |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `import.meta.dir`      | 当前文件所在目录的绝对路径，例如 `/path/to/project`。等价于 CommonJS 模块（及 Node.js）中的 `__dirname`                                                                                                                                                                                         |
| `import.meta.dirname`  | `import.meta.dir` 的别名，兼容 Node.js                                                                                                                                                                                                                                     |
| `import.meta.env`      | `process.env` 的别名                                                                                                                                                                                                                                                    |
| `import.meta.file`     | 当前文件名，例如 `index.tsx`                                                                                                                                                                                                                                                 |
| `import.meta.path`     | 当前文件的绝对路径，例如 `/path/to/project/index.ts`。等价于 CommonJS 模块（及 Node.js）中的 `__filename`                                                                                                                                                                                   |
| `import.meta.filename` | `import.meta.path` 的别名，兼容 Node.js                                                                                                                                                                                                                                    |
| `import.meta.main`     | 指示当前文件是否是当前 `bun` 进程的入口点。即是否由 `bun run` 直接执行，还是被导入                                                                                                                                                                                                                   |
| `import.meta.resolve`  | 将模块标识符（例如 `"zod"` 或 `"./file.tsx"`）解析为 URL。等效于浏览器中的[`import.meta.resolve`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#resolve)。例如：`import.meta.resolve("zod")` 返回 `"file:///path/to/project/node_modules/zod/index.ts"` |
| `import.meta.url`      | 当前文件的字符串 URL，例如 `file:///path/to/project/index.ts`。等效于浏览器中的[`import.meta.url`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url)                                                                                          |
