Bun

Bun v1.1.22


Ashcon Partovi · 2024 年 8 月 7 日

Bun v1.1.22 版本在此!此版本修正了 79 個錯誤(解決了 93 個 👍)。Express 在 Bun 中現在快了 3 倍,ES 模組在 Windows 上載入更快,Bun.serve() 在 POST 請求中快了 10%,以及 bun build --compile 中的原始碼地圖。減少了 --hot、Bun.serve() 和 imports 的記憶體使用量。Uint8Array.toBase64()Uint8Array.toHex(),以及大量的 Node.js 相容性改進和錯誤修正。

我們正在舊金山招聘系統工程師,以建構 JavaScript 的未來!

安裝 Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.dev.org.tw/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install 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

Express 在 Bun 中現在快了 3 倍

在此版本中,我們對 Bun 的 node:http 實作進行了效能和相容性改進。這使得 express 請求吞吐量比 Bun v1.1.21 提高了 50%,這表示在 Bun 中執行相同程式碼比 Node.js 快了 3 倍。

ES 模組在 Windows 上載入速度最高快 4 倍

為了讓 TypeScript、JSX、ES 模組和 CommonJS 都能在 Bun 中正常運作,我們在執行時即時編譯每個檔案。這本身就帶有效能成本。

此版本為 Bundows 帶來了並行轉譯支援,這使得載入 ES 模組更快。

async function benchmark(name) {
  console.time(name);
  await import(name);
  console.timeEnd(name);
}

await benchmark("lodash-es");

在此基準測試中,我們執行了上述程式碼,並測量了匯入熱門套件(如 lodash-es)所需的時間。

執行階段時間 (毫秒)
Bun v1.1.2239
Bun v1.1.2182
Node.js v22.4.1186

此最佳化先前僅在 Linux 和 macOS 的 Bun 上支援,但現在也支援 Windows。

Bun.serve() 中 POST 請求的吞吐量提高 10%

如果您的程式碼不讀取傳入 Request 的主體,則效能比 Bun 的上一個版本提高了 10%。

減少記憶體使用量

bun --hot 使用更少的記憶體

在此版本中,當您使用 bun --hot 重新載入程式碼時,Bun 使用的記憶體減少了 2 倍。

先前,我們並非總是發布舊版模組的原始碼。如果您的程式碼或相依性修改了 require.cache(這是重新載入程式碼的常見模式),情況也是如此。

匯入被垃圾回收的模組使用更少的記憶體

我們修正了一個錯誤,其中匯入或請求被垃圾回收的模組會保留對該模組原始碼的引用。

當模組拋出錯誤時也會發生這種情況,這會保留對模組原始碼的引用。

記憶體減少

我們還修復了各種記憶體洩漏,包括在以下情況:

  • 請求 CommonJS 模組
  • 計算原始碼地圖中的行偏移
  • 並行轉譯超過 64 個檔案
  • 建立大量 setTimeout()setInterval() 計時器

更佳的堆疊追蹤

我們修正了 Bun 中 error.stack 追蹤的呈現方式中的錯誤。有時,呼叫站點的原始碼 URL 會是空的,這會導致堆疊追蹤與大多數工具預期接收到的略有不同。現在,Next.js 可以正確呈現堆疊追蹤。

之前:堆疊追蹤無法在 Next.js 中呈現。
之後:堆疊追蹤在 Next.js 中顯示正確的原始碼。

單一檔案可執行檔中的原始碼地圖

bun build --compile 讓您產生單一檔案可執行檔,將您的原始碼、相依性和 Bun 捆綁到單一檔案中,您可以將其部署到生產環境,而無需其他相依性。

在此 Bun 版本中,單一檔案可執行檔現在已正確支援 --sourcemap。這為您提供更佳的堆疊追蹤以進行偵錯。

bun build cli.ts --compile --sourcemap

之前

/tmp
❯ bun-1.1.21 build --compile --sourcemap errory.ts
[3ms] bundle 1 modules
[110ms] compile errory

/tmp
❯ ./errory
2 | // @bun
3 | // errory.ts
4 | throw new Error("hey " + n);
^
error: hey 42
at hey (/$bunfs/root/errory:4:9)
      at /$bunfs/root/errory:6:4

Bun v1.1.21 (macOS arm64)

之後

❯ bun build  --compile --sourcemap errory.ts
   [1ms]  bundle  1 modules
 [101ms] compile  errory

/tmp
❯ ./errory
1 | function hey(n: number) {
2 |   throw new Error("hey " + n);
            ^
error: hey 42
      at hey (errory.ts:2:9)
      at errory.ts:4:1

Bun v1.1.22-canary.104+c527058f1 (macOS arm64)

為了減少原始碼地圖的記憶體和檔案大小成本,我們使用 zstd 壓縮輸入原始碼,並序列化為自訂二進位格式。

Node.js 相容性

util.getSystemErrorName()

Bun 現在支援 node:util 模組中的 util.getSystemErrorName() 函數。這可讓您傳回錯誤物件的錯誤碼名稱。

import { readFileSync } from "node:fs";
import { getSystemErrorName } from "node:util";

try {
  readFileSync("/does/not/exist");
} catch (error) {
  console.log(getSystemErrorName(error)); // "ENOENT"
}

感謝 @nektro 實作此功能!

util.aborted()

Bun 現在支援 node:util 模組中的 util.aborted() 函數。這可讓您監聽 AbortSignal 並接收在訊號中止時解析的 Promise。

import { aborted } from "node:util";

const controller = new AbortController();
const promise = aborted(controller.signal);

promise.then(() => {
  console.log("Aborted!");
});

events.getEventListeners()

Bun 現在也支援 node:events 模組中 EventTarget 物件的 events.getEventListeners() 函數。這可讓您取得給定發射器上給定事件名稱的所有監聽器。

import { getEventListeners } from "node:events";

const emitter = new EventTarget();
emitter.addEventListener("foo", function foo() {});

console.log(getEventListeners(emitter, "foo"));
// [ [Function: foo] ]

先前,這僅支援 node:events 模組中的 EventEmitter 物件。

NODE_EXTRA_CA_CERTS

Bun 現在支援 NODE_EXTRA_CA_CERTS 環境變數,這可讓您指定一個包含一個或多個 CA 憑證的檔案,以便在驗證 TLS 憑證時使用。這適用於執行階段、bun install 以及 Bun 中所有發出 HTTPS 請求的其他部分。

NODE_EXTRA_CA_CERTS=ca.pem bun install
NODE_EXTRA_CA_CERTS=another.pem bun run script.ts

已修正:在 fs.promises.writeFile() 中支援非同步迭代器

Node.js 在 fs.promises.writeFile() 中具有未記載的非同步迭代器支援。各種套件都依賴此支援,我們決定在 Bun 中支援它。

import { writeFile } from "node:fs/promises";

const iterator = async function* () {
  yield "Hello";
  yield "World";
};
await writeFile("hello.txt", iterator);

已修正:如果 process.on("exit") 拋出錯誤,Bun 現在會以 1 結束

我們修正了一個錯誤,其中如果 exitbeforeExit 事件監聽器拋出錯誤,Bun 將不會傳播正確的結束代碼。

process.on("exit", () => {
  throw new Error("Oh no!");
});

感謝 @nektro 修正了此問題!

已修正:Worker 建構函式會誤解 eval 屬性

存在一個錯誤,其中 Worker 建構函式會誤解明確定義的 eval 屬性。這是 Bun v1.1.13 中引入的迴歸錯誤,現已修正。

const worker = new Worker("console.log('hello!')", { eval: false });

// Before: hello!
// After: BuildMessage: ModuleNotFound

感謝 @billywhizz 修正了此錯誤!

已修正:os.freemem() 現在可在 macOS 上運作

此功能先前已在 Linux 和 Windows 上實作,因此此 API 現在可在所有平台上運作。

感謝 @nektro 修正了此問題!

Web API

Uint8Array.toBase64()

Bun 現在支援 Uint8Array 上的 base64 編碼和解碼方法。這是實作於 WebKit 中的,WebKit 遵循 TC39 stage 3 提案以新增這些功能。

  • Uint8Array.prototype.toBase64()Uint8Array 轉換為 base64 字串
  • Uint8Array.prototype.fromBase64() 將 base64 字串轉換為 Uint8Array
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Unit8Array.fromBase64("AQIDBA=="); // [1, 2, 3, 4, 5]

這些是 Web 標準替代方案,可取代 Node.js 中普遍存在的 Buffer.prototype.toString("base64")Buffer.from(string, "base64") 模式。

Uint8Array.toHex()

也新增了這些方法,用於在 Uint8Array 與十六進位字串之間進行轉換。

  • Uint8Array.prototype.toHex()Uint8Array 轉換為十六進位字串
  • Uint8Array.prototype.fromHex() 將十六進位字串轉換為 Uint8Array
new Uint8Array([1, 2, 3, 4, 5]).toHex(); // "0102030405"
Unit8Array.fromHex("0102030405"); // [1, 2, 3, 4, 5]

感謝 @dcrousso@constellation 在 WebKit 中實作這些功能!

bun build

require.main === module 轉換為 import.meta.main

先前,使用 require.main === module 會將模組標記為 CommonJS(因為它使用 module)。現在,Bun 會將其重寫為 import.meta.main,這表示您可以將此模式與 import 語句一起使用。

import * as fs from "fs";

if (typeof require !== "undefined" && require.main === module) {
  console.log("main!", fs);
}
1 | import "fs";
           ^
error: Cannot use import statement with CommonJS-only features
    at /index.js:1:8

note: Try require("fs") instead
note: This file is CommonJS because 'module' was used

這也修正了在使用 CommonJS 模組和 bun build --compile 的單一檔案可執行檔中對 require.main === module 的支援。

感謝 @paperclover 實作這些功能!

--ignore-dce-annotations

某些 JavaScript 工具支援特殊註解,這些註解可能會在無用程式碼消除期間產生影響。例如,@__PURE__ 註解會告知捆綁器函數呼叫是純函數(無論它是否真的是),並且如果未使用該呼叫,則可以將其移除。

let button = /* @__PURE__ */ React.createElement(Button, null);

捆綁或執行上述程式碼將產生一個空檔案,因為未使用 button 變數。

有時,程式庫可能包含不正確的註解,這可能會導致 Bun 移除所需的效果。為了解決這些問題,您可以在執行 bun build 時使用 --ignore-dce-annotations 標誌來忽略所有註解。僅當無用程式碼消除破壞捆綁時才應使用此選項,並且應優先修正註解,而不是保持此標誌開啟。

感謝 @paperclover 實作此功能!

已修正:捆綁中對 module.exports 的賦值

在捆綁 CommonJS 模組時,Bun 會嘗試在可能的情況下將 module.exports 轉換為 exports。這有助於最小化,但對 module.exportsexports 的賦值都必須撤銷最佳化。對於在賦值之前以文字形式出現的 module.exports 實例(這對於函數很重要),這並未發生。

input.ts
function main() {
  console.log(module.exports);
}
module.exports = 123;
main();
out.js
 var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);

 // input.ts
 var require_input = __commonJS((exports, module) => {
   function main() {
-    console.log(exports);
+    console.log(module.exports);
   }
   module.exports = 123;
   main();
 });
 export default require_input();

感謝 @paperclover 修正了此問題!

已修正:最小化 await import("react-dom/server")

已修正當在動態匯入 react-dom 的套件上使用 --minify-identifier 時導致以下錯誤的錯誤

SyntaxError: Cannot declare an imported binding name twice

對於某些套件(如 React),Bun 會套用額外的最佳化以將 CommonJS 轉換為 ESM。在此轉換中,產生的 import 語句具有錯誤的範圍分配,導致最小化器將兩個 import 語句使用相同的最小化變數名稱。

已修正錯誤

我們花費大量時間修正 Bun 中的錯誤。這是因為我們的首要任務是在生產環境中支援 Bun。

如果您遇到我們尚未修正的錯誤,請在 GitHub 上提交問題或為現有問題投票,以便我們可以優先處理它。如果您能夠提供最小的可重現範例,則會有所幫助。

Bun.serve() 中未捕獲錯誤時遺失預覽

當您將 development: true 傳遞給 Bun.serve() 時,我們會啟用內建錯誤處理常式,該處理常式會在處理常式中拋出未捕獲的例外時顯示錯誤預覽。

我們修正了一個錯誤,其中如果 Bun.serve() 中拋出未捕獲的例外,則不會顯示錯誤預覽。這是 Bun v1.1.9 中引入的迴歸錯誤,現已修正。

之前:遺失錯誤
之後:顯示錯誤和堆疊追蹤

TextDecoder 無法正確解碼 192 或 193

我們修正了一個錯誤,其中 TextDecoder 無法正確解碼以 192193 開頭的 UTF-8 序列。

const decoder = new TextDecoder();
const bytes = new Uint8Array([192, 191]);

console.log(decoder.decode(bytes));
// Before: "?"
// After: "\uFFFD\uFFFD"

帶有加法運算子的非 ASCII 範本字串

我們修正了一個轉譯器錯誤,其中在某些情況下,帶有加法運算子和非 ASCII 輸入的範本字串無法正確轉譯。這是 Bun v1.1.17 中引入的迴歸錯誤。

console.log(`${123}➖` + "123456 456");
// Before: 123➖
// After: 123➖123456 456

感謝 @paperclover 修正了此錯誤!

Bun.serve() 可靠性改進

我們對 Bun.serve() 進行了多項可靠性改進。這包括

  • 當 HTTP 請求失敗或中止時,不再建立不必要的 DOMException 物件
  • 修正了透過非同步迭代器取用 ReadableStream 時拋出錯誤可能會導致拋出無法捕獲的全域錯誤的錯誤
  • 不必要的突然 TLS 關閉導致用戶端在某些情況下無法接收 WebSocket 關閉幀
  • 在某些情況下,提供 Bun.file() 會從回應主體中省略尾隨換行符
  • 修正了在仍有開啟的 HTTP 請求時突然停止 Bun.serve() 伺服器時可能發生的崩潰

WebSocket 伺服器在關閉後發布

修正了一個錯誤,其中在已關閉的 ServerWebSocket 上呼叫 .publish() 會錯誤地嘗試發布訊息,而不是傳回 0。在極少數情況下,這可能會導致崩潰。

WebSocket 用戶端接收到偽造的 1006 關閉代碼

我們修正了 WebSocket 用戶端中的一個錯誤,其中即使在向伺服器傳送具有不同代碼的關閉幀後,它也會收到偽造的 1006 關閉代碼。這是因為 Bun 正在執行快速關閉,這表示關閉幀有時未正確傳送。

感謝 @cirospaciari 修正了此錯誤!

傳送 Bun.file() 作為回應時遺失 Date 標頭

我們修正了一個錯誤,其中在 Bun.serve() 中將 File 作為回應傳送時,未傳送 Date 標頭。這是因為 Bun 使用 sendfile() 系統呼叫來傳送檔案,並且該程式碼路徑未設定 Date 標頭。

const server = Bun.serve({
  async fetch(req) {
    const file = Bun.file("hello.txt");
    return new Response(file);
  },
});

const response = await fetch(server.url);
console.log(response.headers.get("Date"));
// Before: undefined
// After: "Wed, 06 Aug 2024 19:46:05 GMT"

感謝 @cirospaciari 修正了此錯誤!

複製的 File 會使用錯誤的檔案名稱

存在一個錯誤,其中從另一個檔案複製 File,同時定義新名稱,會使用錯誤的名稱。現已修正此問題。

const original = Bun.file("original.txt");
const clone = new File([original], "clone.txt");

console.log(clone.name);
// Before: "original.txt"
// After: "clone.txt"

在 Windows 上,當主目錄中有空格時,bun upgrade 會失敗

我們修正了一個錯誤,其中如果您的主目錄包含空格,bun upgrade 將在 Windows 上失敗。

bun upgrade
Bun v1.1.22 is out! You're on v1.1.21
Expand-Archive : A positional parameter cannot be found that accepts argument 'PC\AppData\Local\Temp\1.1.22'.
At line:1 char:47
+ ... lyContinue';Expand-Archive -Path bun.zip C:\Users\My PC\AppDat ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [Expand-Archive], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Expand-Archive

error: Failed to verify Bun (code: FileNotFound))

如果您遇到以下錯誤,則現已修正,但您必須再次執行 PowerShell 安裝程式才能接收修正後的組建

powershell -c "irm bun.sh/install.ps1 | iex"

感謝 @paperclover 修正了此問題!

UDP socket 在 macOS 13 上掛起

Bun 使用未記載的 sendmsg_x() 系統呼叫作為 macOS 上的快速路徑,以便一次傳送多個 UDP 封包,類似於 Linux 上的 sendmmsg() 系統呼叫。我們對 API 的使用在 macOS 13 上造成問題,這會導致 UDP 封包無法傳送。現已修正此問題。

感謝 11 位貢獻者!