如果在工作目录或更高层目录中找不到 node_modules 目录,Bun 将放弃 Node.js 风格的模块解析,转而使用 Bun 模块解析算法。
在 Bun 风格的模块解析下,所有导入的包在执行期间都会自动安装到 全局模块缓存 中(与 bun install 使用的缓存相同)。
import { foo } from "foo"; // 安装 latest 版本
foo();第一次运行此脚本时,Bun 将自动安装 "foo" 并缓存它。下次运行脚本时,将使用缓存的版本。
版本解析
要确定安装哪个版本,Bun 遵循以下算法:
- 检查项目根目录中是否存在
bun.lock文件。如果存在,使用锁文件中指定的版本。 - 否则,向上扫描查找包含
"foo"作为依赖的package.json。如果找到,使用指定的 semver 版本或版本范围。 - 否则,使用
latest。
缓存行为
一旦确定版本或版本范围,Bun 将:
- 检查模块缓存中是否存在兼容版本。如果存在,使用它。
- 解析
latest时,Bun 将检查package@latest是否已在过去 24 小时 内下载并缓存。如果是,使用它。 - 否则,从
npm注册表下载并安装相应版本。
安装
包被安装并缓存到 <cache>/<pkg>@<version>,因此同一包的多个版本可以同时缓存。此外,在 <cache>/<pkg>/<version> 下创建符号链接,以便更快地查找缓存中存在的包的所有版本。
版本说明符
通过在导入语句中直接指定版本或版本范围,可以完全绕过整个解析算法。
import { z } from "zod@3.0.0"; // 特定版本
import { z } from "zod@next"; // npm 标签
import { z } from "zod@^3.20.0"; // semver 范围优势
这种自动安装方法在以下几个方面很有用:
- 空间效率 — 每个依赖版本在磁盘上只存在一处。与每个项目重复安装相比,这节省了大量空间和时间。
- 可移植性 — 要共享简单的脚本和代码片段,你的源文件是 自包含的。无需将代码和配置文件一起
zip打包。通过在import语句中使用版本说明符,甚至不需要package.json。 - 便利性 — 运行文件或脚本之前无需运行
npm install或bun install。只需bun run即可。 - 向后兼容 — 因为 Bun 仍然尊重
package.json中指定的版本(如果存在),你可以通过一个命令切换到 Bun 风格的解析:rm -rf node_modules。
限制
- 没有 Intellisense。IDE 中的 TypeScript 自动完成依赖于
node_modules内部的类型声明文件。我们正在研究各种解决方案。 - 不支持 patch-package
常见问题
这与 pnpm 的做法有何不同?
使用 pnpm 时,你必须运行 pnpm install,这会创建一个符号链接的 node_modules 文件夹供运行时解析。相比之下,Bun 在运行文件时动态解析依赖;无需提前运行任何 install 命令。Bun 也不会创建 node_modules 文件夹。
这与 Yarn Plug'N'Play 的做法有何不同?
使用 Yarn 时,你必须在运行脚本之前运行 yarn install。相比之下,Bun 在运行文件时动态解析依赖;无需提前运行任何 install 命令。
Yarn Plug'N'Play 还使用 zip 文件存储依赖。这使得依赖加载 在运行时更慢,因为对 zip 文件的随机访问读取往往比等效的磁盘查找更慢。
这与 Deno 的做法有何不同?
Deno 要求在每个 npm import 之前使用 npm: 说明符,不支持通过 tsconfig.json 中的 compilerOptions.paths 使用导入映射,并且对 package.json 设置的支持不完整。与 Deno 不同,Bun 目前不支持 URL 导入。