bun install
是一個快速且相容 npm 的套件管理器,可用於 Node.js 或 Bun。
團隊從 npm、pnpm 或 yarn 遷移到 bun install
後,最常分享的回饋意見是關於 Bun 的 bun.lockb
二進制鎖定檔格式。二進制鎖定檔在 Pull Request 中難以審閱。合併衝突變得更難解決。工具程式無法輕易讀取二進制鎖定檔。
為了幫助解決這個問題,我們之前增加了對 bun ./bun.lockb
的支援,以產生相容於 yarn.lock
的鎖定檔,但這還不夠。真實來源仍然是二進制鎖定檔。您必須在二進制鎖定檔上執行 bun
才能取得 yarn 的鎖定檔。這在 Github、工具程式或合併衝突方面效果不佳。
這就是為什麼在 Bun v1.1.39 中,我們推出了 bun.lock
- 一種用於 bun install
的新型文字鎖定檔格式
bun install --save-text-lockfile
這個標記讓 Bun 儲存基於文字的 bun.lock
檔案,而不是儲存二進制的 bun.lockb
檔案。在 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
速度很快,因為我們非常努力使其快速。沒有像二進制鎖定檔格式這樣的「單一因素」使其快速。
Structure of Arrays
我們做了很多工作來避免 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[];
}
我們不是為陣列內部的每個元素使用陣列,而是為每種類型使用一個大陣列並將其附加到其中。這通常稱為 陣列結構。
Small string optimizations
當您有許多通常很小的字串(例如套件名稱或版本)時,您可以將小字串儲存在用於引用它們的相同空間中,而不是單獨分配每個字串。在 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 },
};
Careful 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
VSCode 將為您突出顯示 bun.lock
檔案的語法,感謝 @remcohaszing。
在下一個版本的 @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 團隊更容易添加支援。