Skip to main content
JavaScript 生态系统正处于从 CommonJS 模块向原生 ES 模块(ESM)转型的多年过渡期,不同的运行时和构建工具历来对于导入说明符如何映射到磁盘上的文件存在分歧。Bun 旨在提供一个无需配置即可使用、一致且可预测的模块解析系统。

语法

考虑以下文件。
运行 index.ts 会打印 “Hello world!”。
terminal
这里的 ./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
确切的顺序会根据上下文有所不同:require() 会在 ESM 扩展名(.mts.mjs)之前尝试 CommonJS 扩展名(.cts.cjs),而 node_modules 中的导入会在 TypeScript 扩展名之前尝试 JavaScript 扩展名。上面的列表显示的是本地 ESM import 的顺序。
如果导入路径包含扩展名,Bun 会首先检查完全匹配的文件。如果不存在完全匹配的文件,Bun 会回退到将上述扩展名列表追加到完整路径后进行尝试(因此 ./hello.world 可以解析为 ./hello.world.ts)。
index.ts
为了兼容 TypeScript,还有一条额外规则:如果你从 "*.js""*.jsx" 导入,Bun 也会检查匹配的 *.ts*.tsx 文件;在 node_modules 外部,从 "*.mjs" 导入也会匹配 *.mts。这遵循 TypeScript 编译器的文件扩展名替换规则,该规则允许源文件通过编译输出路径相互引用。请注意,与 TypeScript 不同,Bun 不会将 .cjs 重写为 .cts
index.ts
Bun 同时支持 ES 模块(import/export 语法)和 CommonJS 模块(require()/module.exports)。以下 CommonJS 版本也可以在 Bun 中运行。
不过,建议新项目避免使用 CommonJS。

模块系统

Bun 原生支持 CommonJS 和 ES 模块。对于新项目,推荐使用 ES 模块格式,但 CommonJS 模块在 Node.js 生态系统中仍被广泛使用。 在 Bun 的 JavaScript 运行时中,ES 模块和 CommonJS 模块都可以使用 require。如果目标模块是 ES 模块,require 会返回模块命名空间对象(等价于 import * as)。如果目标模块是 CommonJS 模块,require 会返回 module.exports 对象(与 Node.js 中相同)。

使用 require()

你可以 require() 任何文件或包,甚至是 .ts.mjs 文件。
index.ts
2016 年,ECMAScript 增加了对 ES 模块的支持。ES 模块是 JavaScript 模块的标准。然而,数百万个 npm 包仍然使用 CommonJS 模块。CommonJS 模块使用 module.exports 导出值,通常使用 require 导入。
my-commonjs.cjs
CommonJS 与 ES 模块之间最大的区别在于,CommonJS 模块是同步的,而 ES 模块是异步的。其他区别包括:
  • ES 模块支持顶层 await,而 CommonJS 模块不支持。
  • ES 模块始终处于严格模式,而 CommonJS 模块不是。
  • 浏览器不原生支持 CommonJS 模块,但可以通过 <script type="module"> 原生支持 ES 模块。
  • CommonJS 模块无法进行静态分析,而 ES 模块只允许静态导入和导出。
  • 静态 import 语句会同步执行,就像 CommonJS 的 require 一样。ES 模块也可以通过异步的 import() 函数动态加载,这称为“动态导入”。

使用 import

你可以 import 任何文件或包,甚至是 .cjs 文件。
index.ts

同时使用 importrequire()

在 Bun 中,你可以在同一文件内同时使用 importrequire —— 它们都随时有效。
index.ts

顶层 await

唯一的例外是顶层 await。你不能 require() 使用了顶层 await 的文件,因为 require() 是同步函数。 幸运的是,很少有库使用顶层 await,因此这通常不是问题。但如果你在应用程序代码中使用顶层 await,请确保该文件不会在应用程序的其他位置被 require()。请改用 import动态 import()

导入包

Bun 实现了 Node.js 的模块解析算法,因此可以用裸模块名从 node_modules 中导入包。
index.ts
完整算法请参阅 Node.js 文档。简而言之:如果你从 "foo" 导入,Bun 会沿文件系统向上查找包含 foo 包的 node_modules 目录。

NODE_PATH

Bun 支持 NODE_PATH 用于额外的模块解析目录:
多个路径用平台的分隔符分隔(Unix 是 :,Windows 是 ;):
找到 foo 包后,Bun 会读取其 package.json 来确定包的入口点。Bun 首先读取 exports 字段,并检查以下条件。
package.json
package.json 中最先出现的条件决定了包的入口点。 Bun 支持子路径的 "exports""imports"
package.json
子路径导入和条件导入可以共同使用。
package.json
与 Node.js 一样,在 "exports" 映射中指定任何子路径都会阻止其他子路径被导入;你只能导入显式导出的文件。对于前面的 package.json
index.ts
发布 TypeScript — Bun 支持特殊的 "bun" 导出条件。如果你的库使用 TypeScript 编写, 你可以直接将未经转译的 TypeScript 文件发布到 npm。如果你在 "bun" 条件中指定包的 *.ts 入口点,Bun 会直接导入并执行你的 TypeScript 源文件。
如果未定义 exports,Bun 会回退到传统的顶层入口点字段。在运行时,如果存在 "main"(或隐式的 index.* 文件),Bun 会优先使用它,否则使用 "module"
package.json

自定义条件

--conditions 标志指定从 package.json"exports" 解析包时要使用的条件。 bun build 和 Bun 运行时都支持此标志。
terminal
你也可以在 Bun.build 中以编程方式使用 conditions
build.ts

路径重映射

Bun 支持通过 tsconfig.json 中 TypeScript 的 compilerOptions.paths 重新映射导入路径,这与编辑器配合使用效果良好。如果你不是 TypeScript 用户,可以在项目根目录中使用 jsconfig.json 来实现相同的行为。
tsconfig.json
Bun 还支持 package.jsonNode.js 风格的子路径导入,其中映射路径必须以 # 开头。TypeScript 和编辑器也能解析这些路径,你可以同时使用这两种机制。
package.json
Bun 的 JavaScript 运行时原生支持 CommonJS。当 Bun 的 JavaScript 转译器检测到 module.exports 的使用时,会将该文件视为 CommonJS。随后,模块加载器会将转译后的模块包装在一个如下所示的函数中:
moduleexportsrequire 与 Node.js 中的 moduleexportsrequire 非常相似。它们通过 C++ 中的 with 作用域进行赋值。内部的 Map 会存储 exports 对象,以便在模块完全加载之前处理循环 require 调用。当 CommonJS 模块成功评估后,会创建一个合成的模块记录(Synthetic Module Record),其 default ES 模块导出被设置为 module.exports,而 module.exports 对象中的键(如果是对象)会被重新导出为命名导出。Bun 的打包器工作方式有所不同:它会将 CommonJS 模块包装在一个 require_${moduleName} 函数中,该函数返回 module.exports 对象。

import.meta

import.meta 对象公开当前模块的信息。它是 JavaScript 语言的一部分,但其内容并未标准化:每个“宿主”(浏览器或运行时)都会在 import.meta 对象上实现自己的属性。 Bun 实现了如下属性。
/path/to/project/file.ts