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

# 模块解析

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

JavaScript 生态系统正处于从 CommonJS 模块到原生 ES 模块（ESM）的多年过渡期，不同的运行时和构建工具在历史上对导入说明符如何映射到磁盘上的文件存在分歧。Bun 旨在提供一致且可预测的模块解析系统，无需配置即可工作。

## 语法

考虑以下文件。

<CodeGroup>
  ```ts index.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
  import { hello } from "./hello";

  hello();
  ```

  ```ts hello.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
  export function hello() {
    console.log("Hello world!");
  }
  ```
</CodeGroup>

运行 `index.ts` 会打印 "Hello world!"。

```bash icon="terminal" terminal theme={null}
bun index.ts
Hello world!
```

这里 `./hello` 是一个没有扩展名的相对路径。**带扩展名的导入是可选的，但支持。** 要解析此导入，Bun 按顺序检查以下文件：

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

<Note>
  具体顺序因上下文而异：`require()` 在 ESM 扩展名（`.mts`、`.mjs`）之前先尝试 CommonJS 扩展名（`.cts`、`.cjs`），而 `node_modules` 内的导入优先尝试 JavaScript 扩展名，然后才是 TypeScript 扩展名。上述列表显示了本地 ESM `import` 的顺序。
</Note>

如果导入路径包含扩展名，Bun 首先检查确切的文件。如果没有精确匹配，Bun 会回退到尝试附加到完整路径的上述扩展名列表（因此 `./hello.world` 可以解析为 `./hello.world.ts`）。

```ts index.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { hello } from "./hello";
import { hello } from "./hello.ts"; // 这可以工作
```

还有一个额外的 TypeScript 兼容性规则：如果你从 `*.js` 或 `*.jsx` 导入，Bun 也会检查匹配的 `*.ts` 或 `*.tsx` 文件，并在 `node_modules` 之外，`*.mjs` 也匹配 `*.mts`。这遵循 TypeScript 编译器的[文件扩展名替换](https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution)规则，允许源文件通过其编译后的输出路径相互引用。注意与 TypeScript 不同，Bun 不会将 `.cjs` 重写为 `.cts`。

```ts index.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/javascript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=48bd3119866790424aa113cd09edda91" theme={null}
  const { hello } = require("./hello");

  hello();
  ```

  ```js hello.js icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/javascript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=48bd3119866790424aa113cd09edda91" theme={null}
  function hello() {
    console.log("Hello world!");
  }

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

尽管如此，不推荐在新项目中使用 CommonJS。

***

## 模块系统

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

在 Bun 的 JavaScript 运行时中，ES 模块和 CommonJS 模块都可以使用 `require`。如果目标模块是 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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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` 导入。

  ```ts my-commonjs.cjs icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/javascript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=48bd3119866790424aa113cd09edda91" theme={null}
  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 模块只允许静态导入和导出。
  * 静态 `import` 语句是同步执行的，就像 CommonJS 的 `require`。ES 模块也可以通过异步的 `import()` 函数动态加载，称为"动态导入"。
</Accordion>

### 使用 `import`

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

```ts title="index.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { stuff } from "foo";
```

完整算法在 [Node.js 文档](https://nodejs.org/api/modules.html)中有说明。简而言之：如果你从 `foo` 导入，Bun 会向上扫描文件系统，查找包含包 `foo` 的 `node_modules` 目录。

### NODE\_PATH

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

```bash theme={null}
NODE_PATH=./packages bun run src/index.js
```

```ts theme={null}
// packages/foo/index.js
export const hello = "world";

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

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

```bash theme={null}
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={null}
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node-addons": "./index.js", // 除非传递了 --no-addons
    "node": "./index.js",
    "require": "./index.js", // 如果导入者使用 require()
    "import": "./index.mjs", // 如果导入者使用 import
    "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={null}
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}
```

子路径导入和条件导入可以结合使用。

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

与 Node.js 一样，在 `"exports"` 映射中指定任何子路径都会阻止导入其他子路径；你只能导入那些明确导出的文件。给定前面的 `package.json`：

```ts index.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import stuff from "foo"; // 这可以工作
import stuff from "foo/index.mjs"; // 这不行
```

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

如果没有定义 `exports`，Bun 会回退到遗留的顶级入口点字段。在运行时，Bun 在有 `"main"` 时优先使用它（或隐式的 `index.*` 文件），否则使用 `"module"`。

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

### 自定义条件

`--conditions` 标志指定从 `package.json` 的 `"exports"` 解析包时使用的条件。

`bun build` 和 Bun 的运行时都支持此标志。

```sh terminal icon="terminal" theme={null}
# 与 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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});
```

***

## 路径重映射

Bun 通过 `tsconfig.json` 中的 TypeScript [`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={null}
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"], // 将说明符映射到文件
      "components/*": ["components/*"] // 通配符匹配
    }
  }
}
```

Bun 还支持 `package.json` 中的 [Node.js 风格子路径导入](https://nodejs.org/api/packages.html#subpath-imports)，其中映射路径必须以 `#` 开头。TypeScript 和编辑器也会解析这些路径，你可以同时使用两种机制。

```json package.json icon="file-json" theme={null}
{
  "imports": {
    "#config": "./config.ts", // 将说明符映射到文件
    "#components/*": "./components/*" // 通配符匹配
  }
}
```

<Accordion title="Bun 中 CommonJS 互操作的底层细节">
  Bun 的 JavaScript 运行时原生支持 CommonJS。当 Bun 的 JavaScript 转译器检测到 `module.exports` 的使用时，它会将该文件视为 CommonJS 文件。然后模块加载器将转译后的模块包装成一个形状如下的函数：

  ```js theme={null}
  (function (module, exports, require) {
    // 转译后的模块
  })(module, exports, require);
  ```

  `module`、`exports` 和 `require` 与 Node.js 中的 `module`、`exports` 和 `require` 非常相似。这些是通过 C++ 中的 [`with scope`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) 分配的。一个内部的 `Map` 存储 `exports` 对象以处理模块完全加载之前的循环 `require` 调用。

  一旦 CommonJS 模块成功求值，会创建一个合成模块记录（Synthetic Module Record），其 `default` ES 模块[导出设置为 `module.exports`](https://github.com/oven-sh/bun/blob/9b6913e1a674ceb7f670f917fc355bb8758c6c72/src/bun.js/bindings/CommonJSModuleRecord.cpp#L212-L213)，并且 `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={null}
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` 直接执行则为 `true`，如果被导入则为 `false`                                                                                                                                                                                                 |
| `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`      | 当前文件的 `string` URL，例如 `file:///path/to/project/index.ts`。等同于浏览器中的 [`import.meta.url`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url)                                                                                    |
