bun install
是一个快速的 npm 兼容包管理器,可以与 Node.js 或 Bun 一起使用。
团队从 npm、pnpm 或 yarn 迁移到 bun install
后,最常见的反馈是关于 Bun 的 bun.lockb
二进制锁文件格式。二进制锁文件在拉取请求中很难审查。合并冲突变得更难解决。工具无法轻松读取二进制锁文件。
为了解决这个问题,我们之前添加了对 bun ./bun.lockb
的支持,以生成与 yarn.lock
兼容的锁文件,但这还不够。真理的来源仍然是二进制锁文件。您必须在二进制锁文件上运行 bun
才能获得 yarn 的锁文件。这在 Github、工具或合并冲突中效果不佳。
这就是为什么在 Bun v1.1.39 中,我们引入了 bun.lock
- bun install
的一种新的基于文本的锁文件格式
bun install --save-text-lockfile
这个标志不是保存二进制 bun.lockb
文件,而是使 Bun 保存一个基于文本的 bun.lock
文件。在 Bun v1.2 中,我们计划将其作为默认设置。
{
"lockfileVersion": 0,
"workspaces": {
"": {
"dependencies": {
"uWebSocket.js": "uNetworking/uWebSockets.js#v20.51.0",
},
},
},
"packages": {
"uWebSocket.js": ["uWebSockets.js@github:uNetworking/uWebSockets.js#6609a88", {}, "uNetworking-uWebSockets.js-6609a88"],
}
}
如果首次运行 bun install --save-text-lockfile
时存在 bun.lockb
文件或 package-lock.json
文件,bun 将使用现有的锁文件来生成 bun.lock
文件,从而保留解析和元数据。
缓存的 bun install
速度提升 30%
有些项目开始时比替代方案更快,但随着它们添加缺失的功能和修复错误而变慢。 Bun 不是 其中之一。我们不接受性能倒退。
在 Bun v1.1.39 中,与 Bun v1.1.38 中使用二进制锁文件的缓存 bun install
相比,我们使使用文本锁文件的缓存 bun install
速度提高了 30%。
# --warmup=10
Benchmark 1: bun install --cwd=./with-text # Text-based lockfile
Time (mean ± σ): 45.8 ms ± 2.2 ms [User: 17.4 ms, System: 34.7 ms]
Range (min … max): 43.8 ms … 55.1 ms 60 runs
Benchmark 2: bun-1.1.38 install --cwd=./with-binary # Binary lockfile
Time (mean ± σ): 60.4 ms ± 2.1 ms [User: 14.8 ms, System: 52.1 ms]
Range (min … max): 58.3 ms … 69.9 ms 44 runs
Benchmark 3: cd with-pnpm && pnpm install
Time (mean ± σ): 709.5 ms ± 3.7 ms [User: 914.5 ms, System: 318.7 ms]
Range (min … max): 705.3 ms … 716.1 ms 10 runs
Benchmark 4: cd with-yarn && yarn install
Time (mean ± σ): 243.1 ms ± 3.0 ms [User: 415.9 ms, System: 24.2 ms]
Range (min … max): 240.6 ms … 248.4 ms 12 runs
Benchmark 5: cd with-npm && npm install
Time (mean ± σ): 1.525 s ± 0.174 s [User: 1.459 s, System: 0.119 s]
Range (min … max): 1.275 s … 1.709 s 10 runs
Summary
bun install --cwd=./with-text # Text-based lockfile ran
1.32 ± 0.08 times faster than bun-1.1.38 install --cwd=./with-binary # Binary lockfile
5.31 ± 0.27 times faster than cd with-yarn && yarn install
15.49 ± 0.76 times faster than cd with-pnpm && pnpm install
33.28 ± 4.13 times faster than cd with-npm && npm install
# --warmup=2 --prepare="rm -rf ./with-{text,binary,pnpm,yarn,npm}/node_modules"
Benchmark 1: bun install --cwd=./with-text --ignore-scripts # Text-based lockfile
Time (mean ± σ): 1.590 s ± 0.029 s [User: 0.018 s, System: 0.809 s]
Range (min … max): 1.546 s … 1.651 s 10 runs
Benchmark 2: bun-1.1.38 install --cwd=./with-binary --ignore-scripts # Binary lockfile
Time (mean ± σ): 1.749 s ± 0.024 s [User: 0.015 s, System: 0.882 s]
Range (min … max): 1.719 s … 1.788 s 10 runs
Benchmark 3: cd with-pnpm && pnpm install --ignore-scripts
Time (mean ± σ): 11.303 s ± 0.142 s [User: 4.093 s, System: 107.544 s]
Range (min … max): 10.926 s … 11.442 s 10 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs.
Benchmark 4: cd with-yarn && yarn install --ignore-scripts
Time (mean ± σ): 6.372 s ± 0.104 s [User: 5.980 s, System: 17.191 s]
Range (min … max): 6.286 s … 6.603 s 10 runs
Benchmark 5: cd with-npm && npm install --ignore-scripts
Time (mean ± σ): 8.309 s ± 0.081 s [User: 8.598 s, System: 9.838 s]
Range (min … max): 8.194 s … 8.418 s 10 runs
Summary
bun install --cwd=./with-text --ignore-scripts # Text-based lockfile ran
1.10 ± 0.02 times faster than bun-1.1.38 install --cwd=./with-binary --ignore-scripts # Binary lockfile
4.01 ± 0.10 times faster than cd with-yarn && yarn install --ignore-scripts
5.23 ± 0.11 times faster than cd with-npm && npm install --ignore-scripts
7.11 ± 0.16 times faster than cd with-pnpm && pnpm install --ignore-scripts
{
"name": "desktop",
"type": "module",
"module": "index.ts",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.6.2"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@babel/core": "^7.26.0",
"@octokit/rest": "^21.0.2",
"@sentry/bun": "^8.37.1",
"date-fns": "^4.1.0",
"debug": "^4.3.7",
"express": "^4.21.1",
"gatsby": "^5.14.0",
"ink": "^5.0.1",
"isbot": "^5.1.17",
"next": "^15.1.0",
"postgres": "^3.4.5",
"puppeteer": "^23.10.4",
"ts552": "npm:typescript@5.5.2",
"ts562": "npm:typescript@5.6.2",
"vite": "^5.4.9"
}
}
是什么让 bun install 如此快速?
bun install
速度很快,因为我们非常努力地使其快速。 没有像二进制锁文件格式这样的“一件事”使其快速。
数组结构
我们做了很多工作来避免 O(N^3) 内存分配。 当您有许多要序列化的依赖和嵌套对象/结构(例如包、它们的依赖项、它们的依赖项的依赖项和解析)时,您如何避免单独分配每个对象/结构? 您使用线性可序列化数组的索引而不是指针/对象。
在 TypeScript 中,在包管理器中存储包的缓慢但相对常见的方法如下所示
interface SlowPackage {
name: string;
version: string;
dependencies: Record<string, Dependency>;
/// ... more fields ...
}
interface Workspace {
packages: Record<string, Package>;
root: Package;
}
快速(且过度简化)的版本如下所示
interface Package {
/** Index into strings array */
name: number;
/** Index into strings array */
version: number;
/** Index into dependencies array */
dependenciesStart: number;
/** Length of dependencies array */
dependenciesCount: number;
/** Start offset into resolutions array */
resolutionsStart: number;
/** Length of resolutions array */
resolutionsCount: number;
}
interface Workspace {
packages: Package[];
dependencies: Dependency[];
resolutions: number[];
strings: string[];
}
我们没有为数组内部的每个元素使用数组,而是为每种类型使用一个大数组并附加到它。 这通常称为 数组结构。
小字符串优化
当您有很多通常很小的字符串(例如包名称或版本)时,您可以将小字符串存储在用于引用它们的相同空间中,而不是单独分配每个字符串。 在 JavaScript 等高级语言中,字符串被抽象出来,但在 Zig、C++ 或 Rust 中,“小字符串优化” 是 众所周知的。
在 Zig 中,我们的 semver.String
结构针对小字符串进行了优化
pub const String = extern struct {
pub const max_inline_len: usize = 8;
/// This is three different types of string.
/// 1. Empty string. If it's all zeroes, then it's an empty string.
/// 2. If the final bit is set, then it's a string that is stored inline.
/// 3. If the final bit is not set, then it's a string that is stored in an external buffer.
bytes: [max_inline_len]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
};
细致的 I/O
我们密切关注使用的系统调用。 除非需要,否则我们避免打开目录和读取文件。 我们使用非常具体,有时是不常见的平台特定系统调用,例如 clonefile
、sendfile
、faccessat
、memfd_create
等,以避免不必要的工作。
我可以滔滔不绝地讲很久我们在 bun install
中所做的所有优化,但您明白了。 这从来都不是二进制锁文件格式。 我们只是非常努力地使其快速,并且所有这些工作也适用于基于文本的锁文件格式。
不是一个破坏性更改
我们计划在 Bun v1.2.0 中将 bun.lock
设置为默认值。 在此期间,我们将继续支持二进制 bun.lockb
格式,并将持续一段时间。
在 Bun v1.2 之前,需要使用 bun install --save-text-lockfile
标志来生成基于文本的锁文件。 当 bun.lock
文件存在时,bun install
将使用基于文本的锁文件并忽略二进制锁文件。 否则,它将生成二进制锁文件。
工具兼容性
bun.lock
文件是 JSONC(如 tsconfig.json)
Visual Studio Code
由于 @remcohaszing,VSCode 将为您突出显示 bun.lock
文件的语法。
在下一个版本的 @Code 中
— Bun (@bunjavascript) 2024 年 12 月 13 日
bun.lock 获得了语法高亮,这要归功于 @remcohaszing pic.twitter.com/TFXv5KuhZi
GitHub 和 git
GitHub 在差异中渲染 bun.lock
,这在代码审查时很重要。
以前,GitHub 不会渲染二进制 bun.lockb
文件。
Dependabot
在撰写本文时,Dependabot 的 #1 最受赞成的功能请求 是支持 bun
。 基于文本的锁文件使 Dependabot 团队更容易添加支持。