Bun

Bun v1.0.21


Jarred Sumner · 2024年1月2日

Bun v1.0.21 修復了 33 個錯誤(解決了 80 個 👍 表情符號的回應)。支援 console.table()Bun.write、Bun.file 和 bun:sqlite 使用更少的記憶體。使用 FormData 上傳大型檔案時使用更少的記憶體。bun:sqlite 錯誤訊息更詳細。修復了來自 node:fs 的錯誤中的記憶體洩漏。改進了 Node.js 的相容性,並修復了許多崩潰問題

Bun 是一個速度驚人的 JavaScript 執行時環境、打包器、轉譯器和套件管理器 — 功能All-in-One。如果您錯過了,以下是 Bun 最近的一些變更

  • v1.0.18 - 修復了 27 個錯誤(解決了 28 個 👍 表情符號的回應)。已修復影響 create-vite 和 create-next 和 stdin 的卡頓問題。已修復生命週期腳本回報「node」或「node-gyp」找不到的問題。expect().rejects 現在像 Jest 一樣運作,以及更多錯誤修復
  • v1.0.19 - 修復了 26 個錯誤(解決了 92 個 👍 表情符號的回應)。使用 @types/bun 而不是 bun-types。修復了 --frozen-lockfile 錯誤。bcrypt 和 argon2 套件現在可以運作了。setTimeout 和 setInterval 的吞吐量提高了 4 倍。bun:test 中的模組模擬解析規範。針對 Linux 上大型 stdio 優化了 spawnSync()。Bun.peek() 速度提高了 90 倍,expect(map1).toEqual(map2) 速度提高了 100 倍。修復了 NAPI、bun install 和 Node.js 相容性改進的錯誤
  • v1.0.20 - 降低了 fs.readlinkfs.readFilefs.writeFilefs.statHTMLRewriter 中的記憶體使用量。修復了 setTimeout 在 Linux 上造成 CPU 使用率偏高的回歸問題。HTMLRewriter.transform 現在支援字串和 ArrayBufferfs.writeFile()fs.readFile() 現在支援 hexbase64 編碼。Bun.spawn 顯示了程序使用的 CPU 和記憶體量。

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

現在支援 console.table()

console.table() 是一個 Web API,可從物件或可迭代物件列印表格。它對於除錯很有用。

fetch.js
const { headers } = await fetch("https://example.com");
console.table(headers)

在此範例中,Bun 將列印回應標頭的 ASCII 表格。

┌────┬──────────────────┬───────────────────────────────┐
│    │ 01
├────┼──────────────────┼───────────────────────────────┤
0 │ age              │ 519517
1 │ cache-control    │ max-age=604800
2 │ content-encoding │ gzip                          │
3 │ content-length   │ 648
4 │ content-type     │ text/html; charset=UTF-8
5 │ date             │ Tue, 02 Jan 2024 02:37:00 GMT │
6 │ etag             │ "3147526947+gzip"
7 │ expires          │ Tue, 09 Jan 2024 02:37:00 GMT │
8 │ last-modified    │ Thu, 17 Oct 2019 07:18:26 GMT │
9 │ server           │ ECS (sac/2508)                │
10 │ vary             │ Accept-Encoding               │
11 │ x-cache          │ HIT                           │
└────┴──────────────────┴───────────────────────────────┘

感謝 @otgerrogla 實作此功能!

您也可以將 console.table 與各種不同的物件一起使用,包括

使用 console.table() 的陣列

issues.js
const foo = await fetch("https://api.github.com/repos/elyisajs/elysia/issues");
console.table(await issues.json(), ["title", "status"]);
Screenshot of console.table output

使用 console.table() 的物件

user.js
const user = await fetch("https://api.github.com/users/ThePrimeagen");
console.table(await user.json());

這會印出類似以下的內容

┌─────────────────────┬──────────────────────────────────────────────────────────────────┐
│                     │ Values                                                           │
├─────────────────────┼──────────────────────────────────────────────────────────────────┤
│               login │ ThePrimeagen                                                     │
│                  id │ 4458174                                                          │
│             node_id │ MDQ6VXNlcjQ0NTgxNzQ=                                             │
│          avatar_url │ https://avatars.githubusercontent.com/u/4458174?v=4              │
│         gravatar_id │                                                                  │
│                 url │ https://api.github.com/users/ThePrimeagen                        │
│            html_url │ https://github.com/ThePrimeagen                                  │
│       followers_url │ https://api.github.com/users/ThePrimeagen/followers              │
│       following_url │ https://api.github.com/users/ThePrimeagen/following{/other_user} │
│           gists_url │ https://api.github.com/users/ThePrimeagen/gists{/gist_id}        │
│         starred_url │ https://api.github.com/users/ThePrimeagen/starred{/owner}{/repo} │
│   subscriptions_url │ https://api.github.com/users/ThePrimeagen/subscriptions          │
│   organizations_url │ https://api.github.com/users/ThePrimeagen/orgs                   │
│           repos_url │ https://api.github.com/users/ThePrimeagen/repos                  │
│          events_url │ https://api.github.com/users/ThePrimeagen/events{/privacy}       │
│ received_events_url │ https://api.github.com/users/ThePrimeagen/received_events        │
│                type │ User                                                             │
│          site_admin │ false                                                            │
│                name │ ThePrimeagen                                                     │
│             company │ CEO Of TheStartup                                                │
│                blog │ http://twitch.tv/ThePrimeagen                                    │
│            location │ 9th Ring, Vim                                                    │
│               email │ null                                                             │
│            hireable │ null                                                             │
│                 bio │ null                                                             │
│    twitter_username │ ThePrimeagen                                                     │
│        public_repos │ 199                                                              │
│        public_gists │ 0                                                                │
│           followers │ 23186                                                            │
│           following │ 3                                                                │
│          created_at │ 2013-05-17T15:05:59Z                                             │
│          updated_at │ 2023-11-22T19:47:49Z                                             │
└─────────────────────┴──────────────────────────────────────────────────────────────────┘

bun:sqlite 具有更詳細的錯誤訊息

先前,bun:sqlite 可能會拋出類似以下的錯誤

7 | db.exec('INSERT INTO foo VALUES ("hello")');
    ^
error: constraint failed
      at run (bun:sqlite:185:11)
      at /private/tmp/sqlite.js:7:1

但現在,您將獲得更詳細的錯誤訊息,例如

7 | db.exec('INSERT INTO foo VALUES ("hello")');
    ^
SQLiteError: UNIQUE constraint failed: foo.bar
 errno: 2067
  code: "SQLITE_CONSTRAINT_UNIQUE"

      at run (bun:sqlite:185:11)
      at /private/tmp/fetch.js:7:1

變更了什麼?

  • error.name 現在是 SQLiteError 而不是 Error
  • error.message 現在使用來自 SQLite 的擴充錯誤訊息,其中經常包含表格和欄位名稱
  • error.errno 是來自 SQLite 的擴充錯誤代碼
  • error.code 是來自 SQLite 的擴充錯誤代碼,以字串形式表示
  • error.byteOffset 報告 SQL 陳述式中發生錯誤的位元組偏移量(如果可用)
+ SQLiteError: UNIQUE constraint failed: foo.bar
- error: constraint failed
+  errno: 2067
+   code: "SQLITE_CONSTRAINT_UNIQUE"
      at run (bun:sqlite:185:11)
      at /private/tmp/sqlite.js:7:1

bun:sqlite 使用更少的記憶體

bun:sqlite 現在會將 SQLite 的記憶體使用量報告給垃圾收集器,這會提示垃圾收集器在必要時更積極地釋放記憶體。

此範例建立 100,000 個預備陳述式,消耗的記憶體減少了 4 倍

記憶體使用量Bun 版本
71 MBBun v1.0.21
287 MBBun v1.0.20
import { Database } from "bun:sqlite";

const db = new Database(":memory:");

db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, content TEXT)");
db.exec('INSERT INTO test VALUES (NULL, "hello")');

for (let i = 0; i < 100_000; i++) {
  db.prepare(`SELECT content as content${i} FROM test`);
}

console.log((process.memoryUsage.rss() / 1024 / 1024) | 0, "MB");

我們也修復了綁定實作中的一個錯誤,該錯誤導致內部 SQLStatement 物件在垃圾回收時未呼叫預備陳述式的解構函式。

已修復:bun:sqlite 在使用 latin1 字元時崩潰

當在查詢中使用 latin1 補充字元時,bun:sqlite 中存在崩潰問題,現已修復。

const db = new Database(":memory:");

db.run(
  "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT, copyright© TEXT)",
);

© 符號是有效的 latin1 補充字元,先前將查詢傳送至 SQLite 的程式碼錯誤地假設 latin1 字串始終為 ASCII 字串。

Linux 上的寫入時複製檔案上傳

當您使用 FormData 上傳大型檔案時,每個檔案都會成為記憶體中的 Blob 物件(或 File 物件)。

如果您需要以 ArrayBuffer 格式取得資料,會發生什麼情況?

const blob = formData.get("image");

// This clones the data, doubling the memory usage!
const bytes = await blob.arrayBuffer();

它會複製。突然間,**如果您上傳了 128 MB 的檔案,您現在將使用 256 MB 的記憶體**,僅僅是為了以 ArrayBuffer 格式取得資料。

有人可能會認為這是 API 設計問題。.arrayBuffer() 真的 應該複製資料嗎?但那是標準 Web API,我們必須使用它。

**我們可以在不變更 API 的情況下做得更好嗎?** 可以!

這稱為寫入時複製記憶體。

透過一些虛擬記憶體技巧,我們可以讓您在呼叫 blob.arrayBuffer() 時,它實際上不會複製資料。相反地,它會建立一個指向相同資料的新虛擬記憶體對應,並且僅在您寫入時才複製資料(以 4 KB 的區塊為單位)。

此版本實作了寫入時複製 blob.arrayBuffer() 的支援,當 blob 至少為 8 MB 時(啟用 --smol 時為 1 MB)。

100 個副本後的記憶體使用量Bun 版本
192 MBBun v1.0.21
12.9 GBBun v1.0.20
blob-arrayBuffer.js
const blob = new Blob([new Uint8Array(1024 * 1024 * 128).fill(42)]);

const arrayBuffers = await Array.fromAsync({ length: 100 }, () =>
  blob.arrayBuffer(),
);

console.log("RSS", (process.memoryUsage.rss() / 1024 / 1024) | 0, "MB");

如果您建立 128 MB Blob 的 100 個副本,它只會使用 192 MB 的記憶體。以前,它會使用 12,993 MB MB 的記憶體。

當然,這有額外負擔。為每個 Blob 建立唯一的記憶體對應很耗費資源。Bun 僅對大於 8 MB 的 Blob 執行此操作(或啟用 --smol 時為 1 MB,因為 --smol 優化了記憶體使用量而不是執行時間)。

使用 Bun.CryptoHasherFormData 雜湊檔案上傳

Bun.CryptoHasher 現在支援 Blob 物件,這簡化了從 FormData 雜湊檔案上傳。

import { serve, CryptoHasher } from "bun";

serve({
  async fetch(req) {
    const formData = await req.formData();

    const image = formData.get("file");
    const sha1 = CryptoHasher.hash("sha1", image, "hex");

    return new Response(`SHA-1: ${sha1}`);
  },
});

先前,您必須先將 Blob 轉換為 ArrayBuffer,然後才能進行雜湊。

import { serve, CryptoHasher } from "bun";

serve({
  async fetch(req) {
    const formData = await req.formData();

     const image = formData.get("file");
     const image = await formData.get("file").arrayBuffer();

    const sha1 = CryptoHasher.hash("sha1", image, "hex");

    return new Response(`SHA-1: ${sha1}`);
  },
});

請注意,我們尚未將 Bun.file() 的支援新增至 Bun.CryptoHasher。這很棘手,因為 Bun.file() 是非同步的,而 Bun.CryptoHasher 是同步的。我們將在未來版本中研究新增對其的支援。

expect(...).toBeObject 是 bun:test 中的新匹配器

toBeObject 匹配器現在可在 bun:test 中使用。

import { test, expect } from "bun:test";

test("object is an object", () => {
  expect({}).toBeObject();
});

test("number is not an object", () => {
  expect(1).not.toBeObject();
});

感謝 @coratgerl 新增此匹配器!

已修復:Bun.file() 中的記憶體洩漏

先前,以下程式碼會造成記憶體洩漏

import { file } from "bun";

for (let i = 0; i < 100_000; i++) {
  file("/tmp/" + i + "/foo.txt");
}

檔案路徑字串被重複複製,但未釋放。現在已修復。

已修復:Bun.write() 中的記憶體洩漏

與上述類似,重複呼叫 Bun.write 會造成記憶體洩漏。

import { write } from "bun";

for (let i = 0; i < 100_000; i++) {
  await write("/tmp/" + i + "/foo.txt", "Hello!");
}

同樣地,檔案路徑字串被重複複製,但未釋放。現在已修復。

已修復:來自 node:fs 的錯誤中的記憶體洩漏

當您呼叫 fs.readdir(path, { throwIfNoEntries: true }) 且目錄不存在時,該錯誤訊息會造成記憶體洩漏。

這已修復。

此洩漏並非特定於 readdirSync,幾乎所有來自 node:fs 的錯誤(包含檔案路徑字串或動態產生的訊息)都會發生此情況。這包括 fs.readFilefs.writeFilefs.statfs.readlink 等。

已修復:bun build 現在支援 --public-path

bun build 現在支援 --public-path 旗標,可讓您指定要從中提供資產的公開路徑。

bun build ./entry.ts --public-path=/assets/ --outdir=dist

如果您從 CDN 提供資產並想要確保資產使用絕對 URL,這會很有用。

此功能已在 Bun.build 中實作,使用 JavaScript API,但錯誤地從 CLI 中省略。

已修復:在某些情況下,重新導向後 fetch() 崩潰

已修復有時在快速連續進行多個 fetch() 呼叫(其中一些呼叫會重新導向)時發生的崩潰問題,感謝 @cirospaciari

已修復:當伺服器以非 101 狀態代碼回應時,WebSocket 用戶端崩潰

已修復 WebSocket 用戶端中的一個錯誤,該錯誤可能會在伺服器以非預期的 HTTP 回應標頭回應時導致崩潰。此錯誤在 macOS 上比在 Linux 上更頻繁發生。

已修復:當 TLS 交握失敗時,WebSocket 用戶端崩潰

已修復 WebSocket 用戶端中的一個錯誤,該錯誤可能會在伺服器以無效的 TLS 憑證回應時導致崩潰。此錯誤在 macOS 上比在 Linux 上更頻繁發生。

已修復:當 TLS 交握失敗時,Bun.listen 和 Bun.connect 崩潰

與上述類似,已修復 Bun.listenBun.connect 中的一個錯誤,該錯誤可能會在伺服器以無效的 TLS 憑證回應時導致崩潰。

已修復:bun test 中錯誤訊息中存在無效的代理配對時可能崩潰

當錯誤訊息在 bun test 中包含無效的代理配對(或其他無效的 UTF-8)時,可能會在將錯誤訊息轉換為 UTF-8(在錯誤訊息到達終端機之前)時,由於判斷提示失敗而導致崩潰。這已修復。

已修復:Bun.serve 中多餘的終止區塊

已修復 Bun.serve 中的一個錯誤,該錯誤可能會在串流時導致傳送額外的終止空區塊,感謝 @cirospaciari

已修復:console.log() 輸出中缺少 "call" 屬性

已修復 console.log() 中的一個錯誤,該錯誤導致輸出中缺少 "call" 屬性。

console.log({ call: "where did i go??", a: 1 });

之前

{
  a: 1,
}

之後

{
  call: "where did i go??",
  a: 1,
}

已修復:expect(error).toStrictEqual() 僅比較訊息和名稱

expect(error).toStrictEqual() 匹配器現在比較錯誤的 messagename 屬性,而不是所有屬性。

import { test, expect } from "bun:test";

test("errors match up", () => {
  const err = new Error("foo");
  err.stack;
  expect(err).toStrictEqual(new Error("foo"));
});

此測試現在通過,這使行為與 Jest 和 node 的 assert 模組保持一致。感謝 @Kkaioduarte 修復此錯誤!

已修復:bun install 中的「重複依賴項」現在是警告而不是錯誤

當您執行 bun install 且 package.json 中定義了重複的依賴項時,現在會發出警告而不是錯誤。

bun install
bun install v1.0.21 (f721bbe3)
6 |     "@types/bun": "latest",
        ^
warn: Duplicate dependency: "@types/bun" specified in package.json
   at errory/package.json:6:5

10 |       "@types/bun": "latest"
                         ^
note: "@types/bun" originally specified here
   at errory/package.json:10:21

感謝 @knightspore 修復此錯誤!

感謝 20 位貢獻者!

完整變更日誌