Bun

Bun v0.7.1


Jarred Sumner · 2023年7月29日

Bun v0.7.1 改善了 Node.js 與 Vite 和框架的相容性,提升了 ES Modules 的載入速度,並修復了一些轉譯器的錯誤。

我們正在招募 C/C++ 和 Zig 工程師,一同打造 JavaScript 的未來! 加入我們的團隊 →

Bun 是一個速度極快的 JavaScript 執行環境、打包器、轉譯器和套件管理器 — 功能All-in-One。在過去幾個月中,我們發布了許多 Bun 的更新,這裡為您整理了重點回顧,以防您錯過。

  • v0.6.11 - 解決了 v0.6.10 的發布版本建置問題。
  • v0.6.12 - Error.stack 中的 Sourcemap 支援、Bun.file().exists() 和 Node.js 錯誤修復。
  • v0.6.13 - 實作了 mock Date、更快的 base64 編碼,以及 WebSocketnode:tls 的修復。
  • v0.6.14 - process.memoryUsage()process.cpuUsage()process.on('beforeExit', cb)process.on('exit', cb) 和崩潰修復
  • v0.7.0 - Web Workers、--smol、structuredClone()、WebSocket 可靠性改進、node:tls 修復等等。

安裝 Bun

curl
npm
brew
docker
curl
curl -fsSL https://bun.dev.org.tw/install | bash
npm
npm install -g bun
brew
brew tap oven-sh/bun
brew install bun
docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

升級 Bun

bun upgrade

ES Modules 載入速度提升 30% - 250%

Bun 的轉譯器現在以平行方式運行,顯著提升了 ES Module 的載入效能。

基準測試
index.mjs
基準測試
❯ hyperfine "bun index.mjs" "~/.bun/bin/bun index.mjs"
Benchmark 1: bun index.mjs # New
  Time (mean ± σ):     237.3 ms ±   8.9 ms    [User: 603.5 ms, System: 129.1 ms]
  Range (min … max):   220.3 ms … 253.4 ms    12 runs

Benchmark 2: ~/.bun/bin/bun index.mjs # Bun v0.7.0
  Time (mean ± σ):     606.7 ms ±  52.9 ms    [User: 554.1 ms, System: 79.7 ms]
  Range (min … max):   565.4 ms … 744.1 ms    10 runs

Benchmark 3: node ./index.mjs # Node.js v20.4.0
  Time (mean ± σ):     640.7 ms ±  42.2 ms    [User: 744.6 ms, System: 212.2 ms]
  Range (min … max):   619.3 ms … 759.0 ms    10 runs

Summary
  'bun index.mjs' ran
    2.56 ± 0.24 times faster than '~/.bun/bin/bun index.mjs'
    2.70 ± 0.20 times faster than 'node ./index.mjs'

index.mjs
// Importing 10 copies of Three.js
console.log("Hello! #1");
export * as Three1 from "./node_modules/three1/src/Three.js";
export * as Three2 from "./node_modules/three2/src/Three.js";
export * as Three3 from "./node_modules/three3/src/Three.js";
export * as Three4 from "./node_modules/three4/src/Three.js";
export * as Three5 from "./node_modules/three5/src/Three.js";
export * as Three6 from "./node_modules/three6/src/Three.js";
export * as Three7 from "./node_modules/three7/src/Three.js";
export * as Three8 from "./node_modules/three8/src/Three.js";
export * as Three9 from "./node_modules/three9/src/Three.js";
export * as Three10 from "./node_modules/three10/src/Three.js";
console.log("Hello! #2");

並行轉譯器

我們透過讓 Bun 的轉譯器並行運行,提升了 ES Modules 的載入速度。此變更同時影響了 bun test 和 Bun 的執行環境。

Bun 會轉譯每個檔案。雖然 Bun 的轉譯器速度很快,但當匯入大量模組後,從磁碟讀取檔案、解析 JavaScript、TypeScript 或 JSX、列印輸出程式碼以及產生 sourcemap 的成本就會變得顯著。

此變更有助於 Bun 擴展到更大的專案。

目前,並行轉譯僅在以下有限的條件下進行:

  • Import 陳述式
  • 動態 import

CommonJS require 表達式目前還不是並行的。透過 import 匯入的 CommonJS 模組可能是並行的,但它們的依賴項都不是。我們未來可能會進一步調整此行為的細節。

並行性是基於模組的第一次匯入(基本上:「這種載入程式碼的方式是否允許 Promises?」)

此變更的一個有趣的副作用是,模組解析現在只會執行一次。先前,Bun 會針對每個 import 陳述式執行兩次模組解析。第一次是在解析時,第二次是在執行時。現在,模組解析只會在執行時執行。模組解析有快取,因此我不預期這會對效能產生顯著影響,但儘管如此,這仍然很有趣。

vite dev 在 Linux 上啟動速度提升 90%

一系列的修補程式讓 vite dev 在 Linux 上啟動速度提升了 90%!

Linux 上啟動速度提升 90%

macOS 的啟動效能也有所提升,但仍有許多工作尚待完成。

bun:sqlite 在回傳資料時速度提升 10%

我們針對 bun:sqlite 中的物件建立進行了最佳化,這使得我們的 sqlite 基準測試效能提升了 10%。

Workspace 套件現在可以在依賴項中指定 npm 版本

bun install 現在支援在 dependencies 中指定 npm 版本範圍以進行比對的 workspace 套件。

packages/react-query/package.json
packages/query-core/package.json
package.json
packages/react-query/package.json
{
  "name": "@tanstack/react-query",
  "version": "4.32.0",
  "dependencies": {
    "@tanstack/query-core": "^4.32.0"
  }
}
packages/query-core/package.json
{
  "name": "@tanstack/query-core",
  "version": "4.32.0",
}
package.json
{
  "name": "@tanstack/monorepo",
  "version": "4.32.0",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
}

這讓程式庫作者可以針對本機開發和發布到 npm 使用相同的 package.json。當本機儲存庫中的 workspace 成員套件符合 npm 版本範圍時,Bun 將會使用本機 workspace 套件。否則,它將會使用遠端 npm 套件。

改進了對私有 registry 和 Verdaccio 的支援

先前,Bun 的 http 用戶端有時無法連線到在 Node.js 中可運作的主機名稱。這是因為未將與 Node.js 相同的選項傳遞給 getaddrinfo,並且僅檢查 getaddrinfo 傳回的第一個位址,而不是全部。

這個錯誤最常表現為 Bun 無法連線到私有 npm registry,因為這些 registry 更常位於具有特殊 DNS 設定的私有網路中。

Verdaccio 是一個流行的私有 npm 代理 registry 伺服器,感謝修復此錯誤,您可以使用它作為 bun install 的 registry。

修復了 /absolute/path/to/bun.lockb 的錯誤

Bun 的 lockfile 是二進位格式。為了方便檢查,Bun 將其標記為可執行檔,您可以執行 ./bun.lockb 來取得列印出的 Yarn v1 風格 lockfile。

然而,之前存在一個錯誤,以這種方式傳遞絕對路徑會無法運作。現在已修復。

之前

/Users/jarred/Code/bun/bun.lockb
lockfile not found:

之後

/Users/jarred/Code/bun/bun.lockb
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
# bun ./bun.lockb --hash: BB257457B48409D5-2e33f90ba679b81f-4C68A5605FDDA532-9220e96d0851dbdf

...

Node.js 相容性改進

我們正努力使 Bun 更相容於 Vite 驅動的框架。

fs.watch 可靠性改進

@cirospaciari 發布了幾個修補程式,提升了 fs.watch 的可靠性和效能。

  • 先前,vite dev 會啟動 11 個執行緒來監看檔案。現在,只需要 1 個執行緒。
  • 修復了發生權限錯誤時的崩潰問題。
  • 在 macOS 上,指向目錄的符號連結並非總是會被監看。現在已修復。
  • 在 macOS 上,當持續監看目錄時,watcher 有時會 panic。這已修復。
  • fs.promises.watch 不再導致程序永遠保持運行

path.format 錯誤修復

path.format 中的一個錯誤導致 vite build 為 css 檔案輸出 undefined

@paperclover 在 #3734 中修復了這個問題

path.join latin1 編碼問題

當將非 ascii latin1 字串傳遞給 path.join 時,其中的一個錯誤可能會傳回編碼不正確的路徑。

path.dirname 為根路徑傳回 '.'

path.dirname 為根路徑傳回 '.',這導致某些套件無法與 Bun 搭配使用。

感謝 @Hanaasagi 修復了這個問題。

StringDecoder toString latin1 錯誤修復

以下程式碼片段在 Bun 和 Node.js 中產生不同的輸出

const { StringDecoder } = require("string_decoder");
const assert = require("assert");

const buf = Buffer.from("dd", "hex");

const sd = new StringDecoder("latin1");

const s = sd.write(buf);

console.log(s);

assert.strictEqual(s, "Ý");

這是錯誤的,並且已修復,感謝 @Hanaasagi

fs.readdir() 在傳回大量檔案時的崩潰錯誤修復

先前,當字串數量約為 1000 個左右時,fs.readdir() 會崩潰。此錯誤是因在原生程式碼中不正確地建立 Array 所導致,這種方式會導致垃圾回收在陣列傳回給 JavaScript 之前釋放該陣列。

async fs.readdir()、fs.stat、fs.lstat、fs.readFile

以下 node:fs 函數現在將使用 Bun 的執行緒池來執行其工作

Promises

  • fs.promises.readdir()
  • fs.promises.stat()
  • fs.promises.lstat()
  • fs.promises.readFile()

Callbacks

  • fs.readdir()
  • fs.stat()
  • fs.lstat()
  • fs.readFile()

先前,Bun 會讓它們傳回預期的值,但實際上並未在單獨的執行緒上執行工作。這有助於稍微縮短 vite dev 的啟動時間。

child_process.fork() 已部分實作

child_process.fork() 已部分實作,感謝 @sirenkovladd。IPC 支援尚未實作,將在未來的版本中新增。

stat() 處理大型檔案

先前,fs.stat() 處理大型檔案時會截斷額外的帶號整數字節,傳回不正確的值。這已修復。

import { statSync } from "fs";

// Before:
statSync("./8gb-file.txt").size; // -635437056

// After:
statSync("./8gb-file.txt").size; // 8589934592

fs.createReadStream() 中遺失最後一個位元組的錯誤修復

一個差一錯誤導致 fs.createReadStream() 中出現一些不正確的行為。這已修復,感謝 @Hanaasagi

這最常在使用 Express 的 compression npm 套件時出現。

Web Platform API 相容性改進

先前,Bun 在 fetch() 和 HTTP 用戶端中未使用 WebKit 的 URL 解析器來解析和正規化 URL。這導致了一些不相容性和錯誤。Bun 現在在 fetch() 和 HTTP 用戶端中使用 WebKit 的 URL 解析器。這也適用於 bun install

MessagePortMessageChannel 現在已支援

MessagePort 和 MessageChannel API 是 Bun 中新的全域變數。感謝 @dylan-conway 使其正常運作,並感謝 WebKit/Safari 團隊的實作。

const { port1, port2 } = new MessageChannel();

port1.onmessage = (e) => {
  console.log(e.data); // "hello!"
};

port2.postMessage("hello!");

Bun 之所以選擇 JavaScriptCore 而非採用伺服器端 V8 單一文化的原因之一,是因為 JavaScriptCore 和 WebKit/Safari 緊密結合。這表示 Bun 通常可以直接使用 WebKit/Safari 的 Web API 實作,而無需重新實作。這就是一個很好的例子。

fetch() 現在支援 file: URL

您現在可以使用 fetch() 從本機檔案系統載入檔案。

const res = await fetch(new URL("hello!.jpg", import.meta.url));
await res.arrayBuffer();

在內部,這大致等同於

import { join } from "path";
const arrayBuffer = await Bun.file(
  join(import.meta.dir, "hello!.jpg"),
).arrayBuffer();

Web Crypto API 現在會保持事件迴圈運作

先前,以下程式碼永遠不會記錄「finished」

crypto.subtle.digest("SHA-256", new Uint8Array([1, 2, 3])).then(() => {
  console.log("finished");
});

由於從未等待 crypto.subtle.digest 傳回的 promise,Bun 的事件迴圈會在 Web Crypto API 完成其工作之前退出。此錯誤已修復。現在,上述程式碼將如預期般記錄「finished」。

WebAssembly 和 Atomics 錯誤修復

Bun 的事件迴圈現在將等待 WebAssembly 完成載入後再退出程序。

先前,以下程式碼片段永遠不會記錄

import Parser from "web-tree-sitter";

const main = async () => {
  console.log("start");
  await Parser.init();
  console.log("finished");
};

main();

您必須 await main()。無論您是否偏好以這種方式運作,現狀都是一個錯誤,因為其他執行環境並非如此運作。這導致各種套件無法運作。例如,此錯誤影響了 SolidStart。

Atomics.wait() 也存在類似的錯誤。

我們對 JavaScriptCore 進行了小幅修改,以將任務推送到我們的事件迴圈。在源自 JavaScriptCore 的非同步工作中,僅影響 WebAssembly 和 Atomics。我們不會等待 FinalizationRegistry 執行其回呼,然後再退出程序(因為 FinalizationRegistry 本身就不保證會被呼叫)。

Bun.isMainThreadPromise.withResolvers()

您現在可以使用 Bun.isMainThread 來檢查您是否在主執行緒中。

import { isMainThread } from "bun";

console.log("isMainThread", isMainThread);

if (isMainThread) {
  const worker = new Worker(import.meta.url);
  const { promise, resolve } = Promise.withResolvers();

  worker.addEventListener("open", () => {
    resolve();
  });

  await promise;
}

您也可以使用 Promise.withResolvers() 來建立 promise 及其解析器。這是 TC39 stage3 提案。

expect(arg).pass() 和 expect(arg).fail()

jest-extended matcher expect(arg).pass()expect(arg).fail() 現在已在 bun:test 中支援,感謝 @TiranexDev

import { expect, test } from "bun";

test("expect().pass()", () => {
  expect(1).pass("I have passed!");
});

更新日誌

查看完整更新日誌