语法
考虑以下文件。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 的顺序。./hello.world 可以解析为 ./hello.world.ts)。
index.ts
"*.js" 或 "*.jsx" 导入,Bun 也会检查匹配的 *.ts 或 *.tsx 文件;在 node_modules 外部,从 "*.mjs" 导入也会匹配 *.mts。这遵循 TypeScript 编译器的文件扩展名替换规则,该规则允许源文件通过编译输出路径相互引用。请注意,与 TypeScript 不同,Bun 不会将 .cjs 重写为 .cts。
index.ts
import/export 语法)和 CommonJS 模块(require()/module.exports)。以下 CommonJS 版本也可以在 Bun 中运行。
模块系统
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
什么是 CommonJS 模块?
什么是 CommonJS 模块?
2016 年,ECMAScript 增加了对 ES 模块的支持。ES 模块是 JavaScript 模块的标准。然而,数百万个 npm 包仍然使用 CommonJS 模块。CommonJS 模块使用 CommonJS 与 ES 模块之间最大的区别在于,CommonJS 模块是同步的,而 ES 模块是异步的。其他区别包括:
module.exports 导出值,通常使用 require 导入。my-commonjs.cjs
- ES 模块支持顶层
await,而 CommonJS 模块不支持。 - ES 模块始终处于严格模式,而 CommonJS 模块不是。
- 浏览器不原生支持 CommonJS 模块,但可以通过
<script type="module">原生支持 ES 模块。 - CommonJS 模块无法进行静态分析,而 ES 模块只允许静态导入和导出。
- 静态
import语句会同步执行,就像 CommonJS 的require一样。ES 模块也可以通过异步的import()函数动态加载,这称为“动态导入”。
使用 import
你可以 import 任何文件或包,甚至是 .cjs 文件。
index.ts
同时使用 import 和 require()
在 Bun 中,你可以在同一文件内同时使用 import 和 require —— 它们都随时有效。
index.ts
顶层 await
唯一的例外是顶层 await。你不能require() 使用了顶层 await 的文件,因为 require() 是同步函数。
幸运的是,很少有库使用顶层 await,因此这通常不是问题。但如果你在应用程序代码中使用顶层 await,请确保该文件不会在应用程序的其他位置被 require()。请改用 import 或动态 import()。
导入包
Bun 实现了 Node.js 的模块解析算法,因此可以用裸模块名从node_modules 中导入包。
index.ts
"foo" 导入,Bun 会沿文件系统向上查找包含 foo 包的 node_modules 目录。
NODE_PATH
Bun 支持NODE_PATH 用于额外的模块解析目录:
:,Windows 是 ;):
foo 包后,Bun 会读取其 package.json 来确定包的入口点。Bun 首先读取 exports 字段,并检查以下条件。
package.json
package.json 中最先出现的条件决定了包的入口点。
Bun 支持子路径的 "exports" 和 "imports"。
package.json
package.json
"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
package.json 中 Node.js 风格的子路径导入,其中映射路径必须以 # 开头。TypeScript 和编辑器也能解析这些路径,你可以同时使用这两种机制。
package.json
Bun 中 CommonJS 互操作的底层细节
Bun 中 CommonJS 互操作的底层细节
Bun 的 JavaScript 运行时原生支持 CommonJS。当 Bun 的 JavaScript 转译器检测到
module.exports 的使用时,会将该文件视为 CommonJS。随后,模块加载器会将转译后的模块包装在一个如下所示的函数中:module、exports 和 require 与 Node.js 中的 module、exports 和 require 非常相似。它们通过 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