Bun

Bun v0.7.0


Jarred Sumner · 2023年7月21日

我們很高興地宣布 Bun v0.7.0 版本,這是在 Node.js 相容性方面的一大躍進。

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

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

  • v0.6.10 - fs.watch()bun install 錯誤修正、bun test 功能,以及改進的 CommonJS 支援
  • v0.6.11 - 解決了 v0.6.10 的發布版本問題。
  • v0.6.12 - Error.stack 中的 Sourcemap 支援、Bun.file().exists(),以及 Node.js 錯誤修正。
  • v0.6.13 - 實作了模擬 Date、更快的 base64 編碼,以及 WebSocketnode:tls 的修正。
  • v0.6.14 - process.memoryUsage()process.cpuUsage()process.on('beforeExit', cb)process.on('exit', cb) 和崩潰修正

安裝 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

Vite 支援

此支援仍處於實驗階段且未經最佳化。 即使使用 Bun 執行,Vite 也不會使用 Bun 的打包器、模組解析器或轉譯器。

隨著近期在 Node.js API 相容性方面取得的進展,現在 Bun 可以執行 vite dev 了,感謝 @paperclover!這是 Bun 最受歡迎的問題之一。

若要使用 Vite 的其中一個入門專案嘗試此功能,請使用 bunx

bunx create-vite myapp
cd myapp
bun install

然後啟動開發伺服器。

bun --bun vite dev

為什麼使用 --bun --bun 標記會告知 Bun 覆寫 vite CLI 中的 #! /usr/bin/env node Shebang,並使用 Bun 而非 Node.js 執行檔案。在未來的版本中,這將會是預設行為。

Vite 的熱模組重載

這是使用 Bun 的 API 在伺服器端開發前端程式碼,以建構前端應用程式的好方法。

注意:如果您在沒有 -b--bun 的情況下執行 bun vite dev,它仍然會在 Node.js 中執行,因為 vite 的 CLI 在頂部指定了 #!/usr/bin/env node,這會告知 Bun(以及您電腦上的其他軟體)在 Node.js 中執行它。

使用 Worker 實現並行

Bun 現在支援 Worker,讓您可以在獨立的執行緒中執行另一個 JavaScript 實例。在 Bun 中,worker 支援 ES Modules、CommonJS、TypeScript、JSX 以及 Bun 的其他功能,無需額外設定。

如同在瀏覽器中,Worker 是一個全域類別。若要從主執行緒建立 worker

main.ts
const worker = new Worker("./worker.ts");
worker.addEventListener("message", (event: MessageEvent) => {
  console.log("Message from worker:", event.data);
});
worker.postMessage("Hello from main thread!");

在 worker 執行緒中

worker.ts
addEventListener("message", (event: MessageEvent) => {
  console.log("Message from main thread:", event.data);
  postMessage("Hello from worker thread!");
});

此版本包含對 node:worker_threads 模組的支援,但這解除了我們在 Bun 中實作它的必要障礙。

以下全域變數已新增至 Bun

  • postMessage
  • addEventListener
  • removeEventListener
  • onmessage (getter/setter)

請參閱 文件 > API > Workers 以瞭解更多關於在 Bun 中使用 Worker 的資訊。

熱門的 comlink 套件可在 Bun 中運作,無需任何變更。這個函式庫讓在主執行緒和 worker 執行緒之間共享函式和狀態變得更容易。

Comlink 使用範例

structuredClone() 支援

如同在瀏覽器中,postMessage 使用結構化複製演算法序列化訊息。Bun 現在透過 Web 標準的structuredClone() 函式公開此功能,該函式提供了一種深度複製物件的機制。它類似於 JSON.parse(JSON.stringify(obj)),但支援更多類型。

const obj = { a: 1, b: 2 };
const clone = structuredClone(obj);

AsyncLocalStorage 支援

Bun 現在實作了來自 node:async_hooks 模組的 AsyncLocalStorage。這提供了一種機制,用於透過非同步程式碼鏈傳遞上下文資料。這是朝向支援 Next.js 和其他依賴此模組的框架邁出的一大步。

import { AsyncLocalStorage } from "node:async_hooks";

const requestId = new AsyncLocalStorage();
let lastId = 0;

Bun.serve({
  fetch(request) {
    lastId++;
    // Run the callback with 'requestId' set. async_hooks will preserve
    // this value through any chain of asynchronous code.
    return requestId.run(lastId, async () => {
      console.log(`Request ID: ${requestId.getStore()}`);
      await Bun.sleep(500);
      // Even if new requests mutate 'lastId', 'requestId' is still preserved.
      return new Response(`Request ID: ${requestId.getStore()}`);
    });
  },
});

使用 bun --smol 減少記憶體用量

bun --smol 是一個新的 CLI 標記,它將 JavaScriptCore 堆積大小配置為更小且成長更慢,但會犧牲執行時期效能。這對於在記憶體受限的環境中執行 Bun 非常有用。

為了避免手動設定標記,您可以將其設定為 bunfig.toml 中的預設值。

bunfig.toml
smol = true

[test]
# set it only for tests, if you want
smol = true

--bail in bun test

使用 --bail=1 執行 bun test 將在第一次測試失敗後退出。

bun test --bail 1
bun test v0.7.0

✓ test1 [0.02ms]
test2.test.js:
1 | import {test, expect} from 'bun:test';
2 |
3 | test('test2', () => {
4 |   expect(2).toEqual(3);
      ^
error: expect(received).toEqual(expected)
Expected: 3
Received: 2
      at /Users/colinmcd94/Documents/bun/fun/test/test2.test.js:13:8
✗ test2 [0.18ms]
Ran 2 tests across 2 files. [8.00ms]
Bailed out after 1 failures

這對於 CI 環境或當您想要在第一次失敗後停止執行測試時非常有用。感謝 @TiranexDev 完成此項改進!

Bun.readableStreamToFormData()

Bun 現在公開了一個輔助函式,用於將 ReadableStream 轉換為 FormData

它支援 multipart form data。

import { readableStreamToFormData } from "bun";

// without dashes
const boundary = "WebKitFormBoundary" + Math.random().toString(16).slice(2);

const myStream = getStreamFromSomewhere(); // ...
const formData = await Bun.readableStreamToFormData(stream, boundary);
formData.get("foo"); // "bar"

它也支援 URL 編碼的 form data

import { readableStreamToFormData } from "bun";

const stream = new Response("hello=123").body;
const formData = await readableStreamToFormData(stream);
formData.get("hello"); // "123"

我們新增此功能是為了協助修正一個錯誤,該錯誤會導致當 body 是來自 JavaScript 的 ReadableStream 時,request.formData()response.formData() 掛起。

serializedeserialize in bun:jsc

bun:jsc 模組現在導出 serialize()deserialize(),它們將 JavaScript 物件轉換為 ArrayBuffer 並返回。

import { serialize, deserialize } from "bun:jsc";
import { deepEquals } from "bun";

const obj = { a: 1, b: 2 };
const buffer = serialize(obj);
const clone = deserialize(buffer);

if (deepEquals(obj, clone)) {
  console.log("They are equal!");
}

node:v8 模組導出相同的函式,以便與現有的函式庫相容,這些函式庫在進程之間序列化/反序列化資料。

WebSocket 改進

您現在可以手動發送和接收 WebSocket pingpong 框架。

const ws = new WebSocket("wss://echo.websocket.org");
ws.addEventListener("pong", () => {
  console.log("Received pong");
});
ws.ping();

這適用於 ServerWebSocketWebSocket

nodebuffer 現在是預設的 binaryType

預設情況下,Bun 中 WebSocketServerWebSocketbinaryType 現在是 nodebuffer。這表示 WebSocket 中的二進位資料框架將會是 Buffer 實例,而不是 ArrayBuffer(如同之前)。這是為了符合 ws 套件的行為。

const ws = new WebSocket("wss://echo.websocket.org");

ws.addEventListener("message", (event: MessageEvent) => {
  console.log(event.data instanceof Buffer); // true
});

若要將其改回 ArrayBuffer,請設定 ws.binaryType = "arraybuffer"

const ws = new WebSocket("wss://echo.websocket.org");
ws.binaryType = "arraybuffer";

ws.addEventListener("message", (event: MessageEvent) => {
  event.data; // ArrayBuffer
});

(請注意,在瀏覽器中,預設值為 Blob。)

關閉原因現在能正確傳播

修正了一個錯誤,該錯誤會導致 WebSocket 無法正確傳播來自第三方伺服器的關閉原因。感謝 @Electroid 完成這些改進!

Node.js 相容性改進

此版本針對 Node.js 相容性新增了幾項額外的改進。

node:tlsTLSSocket 的改進

以下方法已在 TLSSocket 類別中實作。感謝 @cirospaciari#3596 中完成這些改進。

  • .getPeerFinished()
  • .getFinished()
  • .getProtocol()
  • .getSharedSigalgs()
  • .isSessionReused()
  • .exportKeyingMaterial()
  • .setMaxSendFragment()
  • .getPeerCertificate()
  • .getCertificate()
  • .enableTrace()
  • .disableRenegotiation()
  • .getCipher()
  • .getEphemeralKeyInfo()
  • .getTLSTicket()
  • .getSession()
  • .setSession()

base64url 雜湊不再是 data: urls

先前,Bun 會將 data:base64, 前置到 crypto.createHash("sha256").digest("base64url") 的輸出。這不是 Node.js 的行為,並且導致了函式庫的問題,這些函式庫預期輸出與 Node.js 的字串相同。

crypto.createHash("sha256").update("abc").digest("base64url");

//        Node.js:  "ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0"
//     Bun v0.7.0:  "ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0"
// <= Bun v0.6.14:  "data:base64,ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0="

使用 process.stdout.columnsprocess.stdout.rows 取得終端機尺寸

process.stdoutprocess.stderr 現在支援讀取終端機視窗的尺寸。

const { columns, rows } = process.stdout;
const [columns, rows] = process.stdout.getWindowSize();
const { columns, rows } = process.stderr;
const [columns, rows] = process.stderr.getWindowSize();

如果您想要一次取得兩個尺寸,也可以使用 process.stdout.getWindowSize()

錯誤修正

#3656 已修正 await new Response(latin1String).arrayBuffer()await Response.json(obj).json() 中的記憶體洩漏

修正後

cpu: Apple M1 Max
runtime: bun 0.7.0 (arm64-darwin)

benchmark                                                        time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------------------------------------------------- -----------------------------
new Response().arrayBuffer() (new string each call, latin1)    12.9 µs/iter      (625 ns … 4.18 ms)      1 µs 567.17 µs 711.79 µs
new Response().arrayBuffer() (new string each call, utf16)    12.85 µs/iter     (1.67 µs … 1.56 ms)   2.17 µs 462.75 µs 621.13 µs
new Response().arrayBuffer() (existing string, latin1)         6.53 µs/iter     (6.21 µs … 7.07 µs)   6.64 µs   7.07 µs   7.07 µs

Peak memory usage: 49 MB

修正前

cpu: Apple M1 Max
runtime: bun 0.7.0 (arm64-darwin)

benchmark                                                        time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------------------------------------------------- -----------------------------
new Response().arrayBuffer() (new string each call, latin1)   13.51 µs/iter       (541 ns … 3.2 ms)   1.92 µs 553.42 µs 709.92 µs
new Response().arrayBuffer() (new string each call, utf16)    13.07 µs/iter     (1.71 µs … 3.43 ms)   2.13 µs 451.21 µs 651.67 µs
new Response().arrayBuffer() (existing string, latin1)         6.25 µs/iter     (5.79 µs … 6.81 µs)    6.4 µs   6.81 µs   6.81 µs

Peak memory usage: 292 MB

#3659 已修正一個模組解析錯誤,該錯誤導致 graphql 套件匯入相同模組的 CommonJS 和 ESM 版本。此問題已透過將 package.json main 欄位順序調整為更接近 Node.js 的方式來修正。

error: Cannot use GraphQLScalarType "String" from another module or realm.

Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.

https://yarn.dev.org.tw/en/docs/selective-version-resolutions

Duplicate "graphql" modules cannot be used at the same time since different
versions may have different capabilities and behavior. The data from one
version used in the function from another could produce confusing and
spurious results.

#3663 已修正 bun:test 生命周期鉤子中的一個錯誤,該錯誤導致當範圍中未定義任何測試時,beforeAllafterAll 無法執行。此問題已修正。

#3670 已修正一個 .env 指向目錄時導致 Bun 崩潰的錯誤。此問題已修正。

#3682 已修正一個與帶有展開運算子的三元運算符相關的 TypeScript 解析器錯誤

更新日誌

#3253feat(bun/test):由 @TiranexDev 實作 「bail」 選項,用於 「bun test」
#3608@paperclover 改進我們的內部 typedefs
#3257@Electroid 改進 WebSocketServerWebSocket
#3630$npm_lifecycle_event 應具有上次呼叫的值,由 @TiranexDev 完成
#3631更新 process 的 docs/types,由 @colinhacks 完成
#3637structured clone,由 @dylan-conway 完成
#3650docs:在 typescript.md 中新增遺失的一行,由 @capaj 完成
#3643修正 #3641,由 @Jarred-Sumner 完成
#3614在建構子中支援 napi_wrap,由 @Jarred-Sumner 完成
#3645實作 Workers,由 @Jarred-Sumner 完成
#3654修正 crypto 的 base64url 編碼,由 @Jarred-Sumner 完成
#3655針對物件,structuredClone / postMessage 的 deserialize 速度提升 20%,由 @Jarred-Sumner 完成
#3626workaround readable-stream 相容性,由 @alexlamsl 完成
#3662[install] 優雅地處理重複的工作區宣告,由 @alexlamsl 完成
#3664package json main 欄位擴展順序,由 @dylan-conway 完成
#3596[tls] 一般相容性改進,由 @cirospaciari 完成
#3667zig 升級,由 @dylan-conway 完成
#3671fix(tls) 修補 checkServerIdentity,由 @cirospaciari 完成
#3672feature(constants) 新增 constants/node:constants 模組和測試 (prisma),使用 prima 5.0.0 + 針對 postgres 使用相同連線,新增 prisma mssql(目前已停用),由 @cirospaciari 完成
#3678@Jarred-Sumner 針對找不到 workspace 依賴項提供更佳的錯誤訊息
#3683將 constants 模組移至 cpp,由 @cirospaciari 完成
#3687fix #3682,由 @dylan-conway 完成
#3680修正 createDecipheriv,由 @cirospaciari 完成
#3688更新根憑證並新增 tls.rootCertificates,由 @cirospaciari 完成
#3089實作 AsyncLocalStorage,由 @paperclover 完成
#3693修正瀏覽器綁定的 string_decoder,由 @paperclover 完成
#3694修正 vite,由 @paperclover 完成
#3698修正 #3670,由 @Jarred-Sumner 完成
#3697支援 response.formData() & request.formData 中的 streams,引入 Bun.readableStreamToFormData(),由 @Jarred-Sumner 完成
#3706改進 FFI 數字類型的類型,由 @colinhacks 完成
#3707修正 Worker 的啟動延遲,由 @cirospaciari 完成
#3709_preload_modules 設定為空陣列,由 @dylan-conway 完成
#3708fix 3702,由 @dylan-conway 完成
#3692將建構子引數傳遞給 TextDecoder,由 @Parzival-3141 完成
#3711修正內建 generator $lazy,由 @paperclover 完成
#3710透過 workaround 修正目錄快取,由 @paperclover 完成
#3713再次修正內建,由 @paperclover 完成
#3714修正 process.exit 狀態碼處理,由 @paperclover 完成
#3715fix isFIFO,由 @dylan-conway 完成
#3717字串跳脫邊緣案例,由 @dylan-conway 完成