Bun

Bun 1.2


Ashcon Partovi · 2025 年 1 月 22 日

Bun 是一個完整的工具組,用於建置和測試全端 JavaScript 和 TypeScript 應用程式。如果您是 Bun 的新手,您可以從 Bun 1.0 部落格文章中了解更多資訊。

Bun 1.2

Bun 1.2 是一個重大更新,我們很高興與您分享。

以下是 Bun 1.2 中變更內容的重點摘要

  • Bun 在 Node.js 相容性方面取得了重大進展
  • Bun 現在具有內建 S3 物件儲存 API:Bun.s3
  • Bun 現在具有內建 Postgres 用戶端:Bun.sql(MySQL 即將推出)
  • bun install 現在使用文字式鎖定檔:bun.lock

我們也使 Express 在 Bun 中快 3 倍

Node.js 相容性

Bun 設計為 Node.js 的直接替換品。

在 Bun 1.2 中,我們開始針對我們對 Bun 所做的每一項變更執行 Node.js 測試套件。從那時起,我們修復了數千個錯誤,以下 Node.js 模組現在通過 Bun 超過 90% 的測試。

對於這些 Node 模組中的每一個,Bun 都通過了超過 90% 的 Node.js 測試套件。

以下是我們如何做到的。

您如何衡量相容性?

在 Bun 1.2 中,我們變更了測試和改進 Bun 與 Node.js 相容性的方式。 之前,我們優先處理並修復回報的 Node.js 錯誤,通常來自 GitHub 問題,有人嘗試使用在 Bun 中無法運作的 npm 套件。

雖然這修復了實際使用者遇到的實際錯誤,但這太像「打地鼠」的方法了。 這阻礙了我們進行必要的重大重構,以便我們有機會達到 100% Node.js 相容性。

那時我們想到:如果我們只是執行 Node.js 測試套件呢?

A screenshot of the Node.js test suite
Node.js 儲存庫中有太多測試,檔案無法全部列在 GitHub 上。

在 Bun 中執行 Node.js 測試

Node.js 在其儲存庫中有數千個測試檔,其中大多數位於 test/parallel 目錄中。 雖然「只是執行」他們的測試似乎很簡單,但它比您想像的更複雜。

內部 API

例如,許多測試依賴 Node.js 的內部實作細節。 在以下測試中,getnameinfo 被存根為始終錯誤,以測試 dns.lookupService() 的錯誤處理。

test/parallel/test-dns-lookupService.js
const { internalBinding } = require("internal/test/binding");
const cares = internalBinding("cares_wrap");
const { UV_ENOENT } = internalBinding("uv");

cares.getnameinfo = () => UV_ENOENT;

為了在 Bun 中執行此測試,我們必須將內部繫結替換為我們自己的存根。

test/parallel/test-dns-lookupService.js
Bun.dns.lookupService = (addr, port) => {
  const error = new Error(`getnameinfo ENOENT ${addr}`);
  error.code = "ENOENT";
  error.syscall = "getnameinfo";
  throw error;
};

錯誤訊息

還有一些 Node.js 測試會檢查錯誤訊息的確切字串。 雖然 Node.js 通常不會變更錯誤訊息,但他們不保證在版本之間不會變更。

const common = require("../common");
const assert = require("assert");

assert.throws(
  () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), -1, 0),
  {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "targetStart" is out of range. It must be >= 0. Received -1'
  }
);

為了解決這個問題,我們必須變更某些測試中的判斷提示邏輯,以檢查 namecode,而不是 message。 這也是 Node.js 中檢查錯誤類型的標準作法。 此外,當 Bun 提供比 Node.js 更多使用者資訊時,我們有時會更新訊息。

{
  name: "RangeError",
  code: "ERR_OUT_OF_RANGE",
  message: 'The value of "targetStart" is out of range. It must be >= 0. Received -1'
  message: 'The value of "targetStart" is out of range. It must be >= 0 and <= 5. Received -1'
},

雖然我們盡力比對 Node.js 的錯誤訊息,但在某些情況下,我們希望提供更有幫助的錯誤訊息,只要 namecode 相同即可。

目前進度

我們已將 Node.js 測試套件中的數千個檔案移植到 Bun。 這表示對於我們對 Bun 所做的每一次提交,我們都會執行 Node.js 測試套件以確保相容性。

A screenshot of Bun's CI where we run the Node.js test suite for every commit.
Bun 的 CI 螢幕擷取畫面,我們在其中針對每次提交執行 Node.js 測試套件。

每天,我們都會在 Bun 中新增越來越多通過的 Node.js 測試,我們很高興很快分享更多關於 Node.js 相容性的進展。

除了修復現有的 Node.js API 之外,我們還新增了對以下 Node.js 模組的支援。

node:http2 伺服器

您現在可以使用 node:http2 來建立 HTTP/2 伺服器。 HTTP/2 對於 gRPC 伺服器也是必要的,而 gRPC 伺服器現在也受到 Bun 的支援。 以前,僅支援 HTTP/2 用戶端。

import { createSecureServer } from "node:http2";
import { readFileSync } from "node:fs";

const server = createSecureServer({
  key: readFileSync("key.pem"),
  cert: readFileSync("cert.pem"),
});

server.on("stream", (stream, headers) => {
  stream.respond({
    ":status": 200,
    "content-type": "text/html; charset=utf-8",
  });
  stream.end("<h1>Hello from Bun!</h1>");
});

server.listen(3000);

在 Bun 1.2 中,HTTP/2 伺服器比 Node.js 快 2 倍。 當我們支援 Bun 的新 API 時,我們會花費大量時間調整效能,以確保它不僅可以運作,而且速度更快。

在 Bun 1.2 和 Node.js 22.13 中執行的「hello world」node:http2 伺服器的基準測試。

node:dgram

您現在可以使用 node:dgram 繫結和連線到 UDP socket。 UDP 是一種低階不可靠的訊息協定,通常由遙測提供者和遊戲引擎使用。

import { createSocket } from "node:dgram";

const server = createSocket("udp4");
const client = createSocket("udp4");

server.on("listening", () => {
  const { port, address } = server.address();
  for (let i = 0; i < 10; i++) {
    client.send(`data ${i}`, port, address);
  }
  server.unref();
});

server.on("message", (data, { address, port }) => {
  console.log(`Received: data=${data} source=${address}:${port}`);
  client.unref();
});

server.bind();

這允許像 DataDog 的 dd-trace@clickhouse/client 等套件在 Bun 1.2 中運作。

node:cluster

您可以使用 node:cluster 來產生 Bun 的多個執行個體。 這通常用於透過跨多個 CPU 核心執行任務來實現更高的輸送量。

以下是如何使用 cluster 建立多執行緒 HTTP 伺服器的範例

  • 主要工作執行緒產生 n 個子工作執行緒(通常等於 CPU 核心數)
  • 每個子工作執行緒都監聽相同的埠(使用 reusePort
  • 傳入的 HTTP 請求會在子工作執行緒之間進行負載平衡
import cluster from "node:cluster";
import { createServer } from "node:http";
import { cpus } from "node:os";

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);

  // Start N workers for the number of CPUs
  for (let i = 0; i < cpus().length; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} exited`);
  });
} else {
  // Incoming requests are handled by the pool of workers
  // instead of the primary worker.
  createServer((req, res) => {
    res.writeHead(200);
    res.end(`Hello from worker ${process.pid}`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}

請注意,reusePort 僅在 Linux 上有效。 在 Windows 和 macOS 上,作業系統不會像人們預期的那樣對 HTTP 連線進行負載平衡。

node:zlib

在 Bun 1.2 中,我們將整個 node:zlib 模組從 JavaScript 重寫為原生程式碼。 這不僅修復了一堆錯誤,而且還使其比 Bun 1.1 快 2 倍

在 Bun 和 Node.js 中使用 node:zlib 的 inflateSync 基準測試。

我們還在 node:zlib 中新增了對 Brotli 的支援,Bun 1.1 中缺少此支援。

import { brotliCompressSync, brotliDecompressSync } from "node:zlib";

const compressed = brotliCompressSync("Hello, world!");
compressed.toString("hex"); // "0b068048656c6c6f2c20776f726c642103"

const decompressed = brotliDecompressSync(compressed);
decompressed.toString("utf8"); // "Hello, world!"

使用 V8 API 的 C++ 附加元件

如果您想將 C++ 附加元件 與您的 JavaScript 程式碼一起使用,最簡單的方法是使用 N-API

但是,在 N-API 存在之前,有些套件在 Node.js 中使用了內部 V8 C++ API。 使其複雜化的是,Node.js 和 Bun 使用不同的 JavaScript 引擎:Node.js 使用 V8(Chrome 使用),而 Bun 使用 JavaScriptCore(Safari 使用)。

以前,像 cpu-features 這樣的 npm 套件依賴這些 V8 API,在 Bun 中無法運作。

require("cpu-features")();
dyld[94465]: missing symbol called
fish: Job 1, 'bun index.ts' terminated by signal SIGABRT (Abort)

為了修復此問題,我們進行了前所未有的工程努力,在 JavaScriptCore 中實作 V8 的公用 C++ API,以便這些套件可以在 Bun 中「正常運作」。 解釋起來太複雜且太過於學術性,我們撰寫了一個 3 部分的部落格 系列,說明我們如何在不使用 V8 的情況下支援 V8 API。

在 Bun 1.2 中,可以匯入像 cpu-features 這樣的套件並使其正常運作。

$ bun index.ts
{
  arch: "aarch64",
  flags: {
    fp: true,
    asimd: true,
    // ...
  },
}

V8 C++ API 非常複雜,難以支援,因此大多數套件仍然會缺少功能。 我們將繼續改進支援,以便像 node-canvas@v2node-sqlite3 這樣的套件將來可以運作。

node:v8

除了 V8 C++ API 之外,我們還新增了對使用 node:v8 的堆積快照的支援。

import { writeHeapSnapshot } from "node:v8";

// Writes a heap snapshot to the current working directory in the form:
// `Heap-{date}-{pid}.heapsnapshot`
writeHeapSnapshot();

在 Bun 1.2 中,您可以使用 getHeapSnapshotwriteHeapSnapshot 來讀取和寫入 V8 堆積快照。 這可讓您使用 Chrome DevTools 來檢查 Bun 的堆積。

您可以使用 Chrome DevTools 來檢視 Bun 的堆積快照。

Express 快 3 倍

雖然相容性對於修復錯誤很重要,但它也有助於我們修復 Bun 中的效能問題。

在 Bun 1.2 中,流行的 express 框架可以比 Node.js 快 3 倍 地為 HTTP 請求提供服務。 這是透過改進與 node:http 的相容性,以及最佳化 Bun 的 HTTP 伺服器而實現的。

使用 Bun.s3 支援 S3

Bun 的目標是成為雲端優先的 JavaScript 執行階段。 這表示支援您在雲端中執行生產應用程式所需的所有工具和服務。

現代應用程式將檔案儲存在物件儲存體中,而不是本機 POSIX 檔案系統中。 當終端使用者將檔案附件上傳到網站時,它不會儲存在伺服器的本機磁碟上,而是儲存在 S3 儲存貯體中。 將儲存體與運算分離可防止一整類可靠性問題:磁碟空間不足、來自繁忙 I/O 的高 p95 回應時間,以及共用檔案儲存體的安全性問題。

S3 是雲端中物件儲存體的 事實標準S3 API 由各種雲端服務實作,包括 Amazon S3、Google Cloud Storage、Cloudflare R2 和數十種其他服務。

這就是 Bun 1.2 新增對 S3 的內建支援的原因。 您可以使用與 Web 標準(如 Blob)相容的 API 從 S3 儲存貯體讀取、寫入和刪除檔案。

從 S3 讀取檔案

您可以使用新的 Bun.s3 API 來存取預設的 S3Client。 用戶端提供 file() 方法,該方法傳回 S3 檔案的延遲參考,這與 Bun 的 File 的 API 相同。

import { s3 } from "bun";

const file = s3.file("folder/my-file.txt");
// file instanceof Blob

const content = await file.text();
// or:
//   file.json()
//   file.arrayBuffer()
//   file.stream()

比 Node.js 快 5 倍

Bun 的 S3 用戶端是以原生程式碼而不是 JavaScript 撰寫的。 當您將其與搭配 Node.js 使用像 @aws-sdk/client-s3 這樣的套件進行比較時,它從 S3 儲存貯體下載檔案的速度快 5 倍。

左:Bun 1.2 與 Bun.s3。 右:Node.js 與 @aws-sdk/client-s3。

將檔案寫入 S3

您可以使用 write() 方法將檔案上傳到 S3。 就這麼簡單

import { s3 } from "bun";

const file = s3.file("folder/my-file.txt");

await file.write("hello s3!");
// or:
//   file.write(new Uint8Array([1, 2, 3]));
//   file.write(new Blob(["hello s3!"]));
//   file.write(new Response("hello s3!"));

對於較大的檔案,您可以使用 writer() 方法來取得執行 多部分上傳的檔案寫入器,因此您不必擔心細節。

import { s3 } from "bun";

const file = s3.file("folder/my-file.txt");
const writer = file.writer();

for (let i = 0; i < 1000; i++) {
  writer.write(String(i).repeat(1024));
}

await writer.end();

預先簽署的 URL

當您的生產服務需要讓使用者將檔案上傳到您的伺服器時,使用者直接上傳到 S3 通常比您的伺服器充當仲介更可靠。

為了實現此目的,您可以使用 presign() 方法來為檔案產生 預先簽署的 URL。 這會產生一個帶有簽章的 URL,允許使用者安全地將該特定檔案上傳到 S3,而不會洩露您的憑證或授予他們對您的儲存貯體的不必要存取權。

import { s3 } from "bun";

const url = s3.presign("folder/my-file.txt", {
  expiresIn: 3600, // 1 hour
  acl: "public-read",
});

使用 Bun.serve()

由於 Bun 的 S3 API 擴充了 File API,因此您可以使用 Bun.serve() 透過 HTTP 提供 S3 檔案。

import { serve, s3 } from "bun";

serve({
  port: 3000,
  async fetch(request) {
    const { url } = request;
    const { pathname } = new URL(url);
    // ...
    if (pathname === "/favicon.ico") {
      const file = s3.file("assets/favicon.ico");
      return new Response(file);
    }
    // ...
  },
});

當您使用 new Response(s3.file(...)) 時,Bun 會將使用者重新導向至 S3 檔案的預先簽署 URL,而不是將 S3 檔案下載到您的伺服器並將其傳送回使用者。

Response (0 KB) {
  status: 302,
  headers: Headers {
    "location": "https://s3.amazonaws.com/my-bucket/assets/favicon.ico?...",
  },
  redirected: true,
}

這為您節省了記憶體、時間以及將檔案下載到伺服器的頻寬成本。

使用 Bun.file()

如果您想使用與本機檔案系統相同的程式碼存取 S3 檔案,則可以使用 s3:// URL 協定來參考它們。 這與使用 file:// 來參考本機檔案的概念相同。

import { file } from "bun";

async function createFile(url, content) {
  const fileObject = file(url);
  if (await fileObject.exists()) {
    return;
  }
  await fileObject.write(content);
}

await createFile("s3://folder/my-file.txt", "hello s3!");
await createFile("file://folder/my-file.txt", "hello posix!");

使用 fetch()

您甚至可以使用 fetch() 從 S3 讀取、寫入和刪除檔案。

// Upload to S3
await fetch("s3://folder/my-file.txt", {
  method: "PUT",
  body: "hello s3!",
});

// Download from S3
const response = await fetch("s3://folder/my-file.txt");
const content = await response.text(); // "hello s3!"

// Delete from S3
await fetch("s3://folder/my-file.txt", {
  method: "DELETE",
});

使用 S3Client

當您匯入 Bun.s3 時,它會傳回一個預設用戶端,該用戶端使用 眾所周知的環境變數進行設定,例如 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY

import { s3, S3Client } from "bun";
// s3 instanceof S3Client

您也可以建立自己的 S3Client,然後將其設定為預設值。

import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "my-access-key-id",
  secretAccessKey: "my-secret-access-key",
  region: "auto",
  endpoint: "https://<account-id>.r2.cloudflarestorage.com",
  bucket: "my-bucket",
});

// Sets the default client to be your custom client
Bun.s3 = client;

使用 Bun.sql 支援 Postgres

就像物件儲存體一樣,生產應用程式經常需要的另一個資料儲存區是 SQL 資料庫。

從一開始,Bun 就有一個內建的 SQLite 用戶端。 SQLite 非常適合較小的應用程式和快速指令碼,在這些情況下,您不想擔心設定生產資料庫的麻煩。

在 Bun 1.2 中,我們透過引入 Bun.sql(具有 Postgres 支援的內建 SQL 用戶端)來擴展 Bun 對 SQL 資料庫的支援。 我們還有一個 提取請求,很快就會新增 MySQL 支援。

使用 Bun.sql

您可以使用 Bun.sql 透過 標記範本字串執行 SQL 查詢。 這可讓您將 JavaScript 值作為參數傳遞到您的 SQL 查詢。

最重要的是,它會逸出字串並使用預先處理的陳述式來防止 SQL 注入。

import { sql } from "bun";

const users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 65 },
];

await sql`
  INSERT INTO users (name, age)
  VALUES ${sql(users)}
`;

讀取列也很容易。 結果會以物件陣列的形式傳回,其中欄名稱作為索引鍵。

import { sql } from "bun";

const seniorAge = 65;
const seniorUsers = await sql`
  SELECT name, age FROM users
  WHERE age >= ${seniorAge}
`;

console.log(seniorUsers); // [{ name: "Bob", age: 65 }]

比其他用戶端快 50%

Bun.sql 是以原生程式碼撰寫的,並具有以下最佳化

  • 自動預先處理的陳述式
  • 查詢管線化
  • 二進位線路協定支援
  • 連線集區
  • 結構快取

最佳化堆疊就像魔獸世界中的增益效果。

結果是,與搭配 Node.js 使用最流行的 Postgres 用戶端相比,Bun.sql 在讀取列時速度快達 50%。

postgres.js 遷移到 Bun.sql

Bun.sql API 的靈感來自流行的 postgres.js 套件。 這使得將您現有的程式碼遷移到使用 Bun 的內建 SQL 用戶端變得容易。

  import { postgres } from "postgres";
  import { postgres } from "bun";

const sql = postgres({
  host: "localhost",
  port: 5432,
  database: "mydb",
  user: "...",
  password: "...",
});

const users = await sql`SELECT name, age FROM users LIMIT 1`;
console.log(users); // [{ name: "Alice", age: 25 }]

Bun 是一個套件管理器

Bun 是一個與 npm 相容的套件管理器,可讓您輕鬆安裝和更新您的 node 模組。 您可以使用 bun install 安裝相依性,即使您使用 Node.js 作為執行階段也是如此。

npm install 替換為 bun install

$ npm install
$ bun install

在 Bun 1.2 中,我們對套件管理器做了迄今為止最大的變更。

bun.lockb 的問題

從一開始,Bun 就使用二進位鎖定檔:bun.lockb

與其他使用文字式鎖定檔(如 JSON 或 YAML)的套件管理器不同,二進位鎖定檔使我們能夠使 bun installnpm 快近 30 倍。

但是,我們發現使用二進位鎖定檔時有很多缺點。 首先,您無法在 GitHub 和其他平台上檢視鎖定檔的內容。 這很糟糕。

如果您收到來自外部貢獻者的提取請求,該請求變更了 bun.lockb 檔案,會發生什麼情況? 您信任它嗎? 可能不會。

這也是假設沒有合併衝突! 對於二進位鎖定檔而言,除了手動刪除鎖定檔並再次執行 bun install 之外,幾乎不可能解決。

這也使得工具難以讀取鎖定檔。 例如,像 Dependabot 這樣的相依性管理工具需要 API 來剖析鎖定檔,而我們沒有提供 API。

Bun 將在很長一段時間內繼續支援 bun.lockb。 但是,由於所有這些原因,我們已決定在 Bun 1.2 中切換到文字式鎖定檔作為預設值。

推出 bun.lock

在 Bun 1.2 中,我們推出了新的文字式鎖定檔:bun.lock

您可以使用 --save-text-lockfile 旗標遷移到新的鎖定檔。

bun install --save-text-lockfile

bun.lock 是一個 JSONC 檔案,它是 JSON,新增了對註解和尾隨逗號的支援。

bun.lock
// bun.lock
{
  "lockfileVersion": 0,
  "packages": [
    ["express@4.21.2", /* ... */, "sha512-..."],
    ["body-parser@1.20.3", /* ... */],
    /* ... and more */
  ],
  "workspaces": { /* ... */ },
}

這使得檢視提取請求中的差異變得容易得多,而尾隨逗號使得發生合併衝突的可能性大大降低。

對於沒有鎖定檔的新專案,Bun 將產生新的 bun.lock 檔案。

對於具有 bun.lockb 檔案的現有專案,Bun 將繼續支援二進位鎖定檔,而不會遷移到新的鎖定檔。 我們將在很長一段時間內繼續支援二進位鎖定檔,因此您可以繼續使用命令,例如 bun addbun update,它將更新您的 bun.lockb 檔案。

bun install 效能提升 30%

您可能會認為,在我們遷移到基於文字的 lockfile 之後,bun install 的速度會變慢。錯了!

大多數軟體專案都會隨著功能的增加而變慢,但 Bun 並非如此。我們花費了大量時間調整和優化 Bun,以便讓 bun install 甚至 更快。

這就是為什麼在 Bun 1.2 中,bun install 比 Bun 1.1 快了 30%。

package.json 中支援 JSONC

您是否曾經在 package.json 中新增了一些內容,但幾個月後忘記了原因?或者想向您的團隊成員解釋為什麼某個依賴項需要特定版本?或者您是否曾經因為逗號而在 package.json 檔案中遇到合併衝突?

通常這些問題都是因為 package.json 是一個 JSON 檔案,這意味著您不能在其中使用註解或尾隨逗號。

package.json
{
  "dependencies": {
    // this would cause a syntax error
    "express": "4.21.2"
  }
}

這是一個糟糕的體驗。像 TypeScript 這樣的現代工具允許在其設定檔(例如 tsconfig.json)中使用註解和尾隨逗號,這非常棒。我們也詢問了社群的看法,似乎現狀需要改變。

在 Bun 1.2 中,您可以在 package.json 中使用註解和尾隨逗號。它就是能用。

package.json
{
  "name": "app",
  "dependencies": {
    // We need 0.30.8 because of a bug in 0.30.9
    "drizzle-orm": "0.30.8", /* <- trailing comma */
  },
}

由於有許多工具會讀取 package.json 檔案,因此我們新增了對 require()import() 這些帶有註解和尾隨逗號的檔案的支援。您不需要更改您的程式碼。

const pkg = require("./package.json");
const {
  default: { name },
} = await import("./package.json");

由於這在 JavaScript 生態系統中尚未廣泛支援,我們建議您「自行承擔風險」使用此功能。但是,我們認為這是正確的方向:讓事情對您來說更輕鬆。

.npmrc 支援

在 Bun 1.2 中,我們新增了對讀取 npm 設定檔的支援:.npmrc

您可以使用 .npmrc 來設定您的 npm 登錄檔並設定作用域套件。這對於企業環境通常是必要的,在這些環境中您可能需要驗證到私有登錄檔的身份。

.npmrc
@my-company:registry=https://packages.my-company.com
@my-org:registry=https://packages.my-company.com/my-org

Bun 將在您專案的根目錄和您的主目錄中尋找 .npmrc 檔案。

bun run --filter

您現在可以使用 bun run --filter 同時在多個工作區中執行腳本。

bun run --filter='*' dev

這將在所有符合 glob 模式的工作區中並行執行 dev 腳本。它還將交錯每個腳本的輸出,以便您可以查看每個工作區在執行時的輸出。

您也可以將多個篩選器傳遞給 --filter,並且您可以直接使用 bun 而不是 bun run

bun --filter 'api/*' --filter 'frontend/*' dev

bun outdated

您現在可以使用 bun outdated 查看哪些依賴項已過時。

它將顯示您的 package.json 依賴項列表,以及哪些版本已過時。「update」欄顯示下一個語義版本匹配的版本,「latest」欄顯示最新版本。

如果您注意到有特定的依賴項要更新,您可以使用 bun update

bun update @typescript-eslint/parser # Updates to "7.18.0"
bun update @typescript-eslint/parser --latest # Updates to "8.2.0"

您也可以篩選要檢查更新的依賴項。只需確保引用模式,這樣您的 shell 就不會將它們展開為 glob 模式!

bun outdated "is-*" # check is-even, is-odd, etc.
bun outdated "@discordjs/*" # check @discordjs/voice, @discordjs/rest, etc.
bun outdated jquery --filter="foo" # check jquery in the `foo` workspace

bun publish

您現在可以使用 bun publish 發佈 npm 套件。

它是 npm publish 的直接替代品,並支援許多相同的功能,例如

  • 讀取 .npmrc 檔案進行身份驗證。
  • 打包 tarball,考慮到多個目錄中的 .gitignore.npmignore 檔案。
  • OTP / 雙重驗證。
  • 處理 package.json 欄位的邊緣情況,例如 binfiles 等。
  • 仔細處理遺失的 README 檔案。

我們還新增了對發佈有用的命令的支援,例如

  • bun pm whoami,它會印出您的 npm 使用者名稱。
  • bun pm pack,它會建立一個 npm 套件 tarball,用於發佈或在本機安裝。

bun patch

有時,您的依賴項有錯誤或缺少功能。雖然您可以 fork 套件,進行更改並發佈它——這需要大量工作。如果您不想維護 fork 呢?

在 Bun 1.2 中,我們新增了對修補依賴項的支援。以下是它的運作方式

  1. 執行 bun patch <package> 以修補套件。
  2. 編輯 node_modules/<package> 目錄中的檔案。
  3. 執行 bun patch --commit <package> 以儲存您的更改。就是這樣!

Bun 會在 patches/ 目錄中產生一個包含您的更改的 .patch 檔案,該檔案會在 bun install 時自動套用。然後,您可以將 patch 檔案 commit 到您的儲存庫,並與您的團隊分享。

例如,您可以建立一個 patch 來用您自己的程式碼替換依賴項。

./patches/is-even@1.0.0.patch
diff --git a/index.js b/index.js
index 832d92223a9ec491364ee10dcbe3ad495446ab80..2a61f0dd2f476a4a30631c570e6c8d2d148d419a 100644
--- a/index.js
+++ b/index.js
@@ -1,14 +1 @@
- 'use strict';
-
- var isOdd = require('is-odd');
-
- module.exports = function isEven(i) {
-   return !isOdd(i);
- };
+ module.exports = (i) => (i % 2 === 0)

Bun 從 node_modules 目錄中複製套件,並使用自身的全新副本。這讓您可以安全地對套件目錄中的檔案進行編輯,而不會影響共用的檔案快取。

更易於使用

我們還進行了一系列小的改進,使 bun install 更易於使用。

CA 憑證

您現在可以為 bun install 設定 CA 憑證。當您需要從您公司的私有登錄檔安裝套件,或者您想要使用自我簽署憑證時,這非常有用。

bunfig.toml
[install]
# The CA certificate as a string
ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# A path to a CA certificate file. The file can contain multiple certificates.
cafile = "path/to/cafile"

如果您不想更改您的 bunfig.toml 檔案,您也可以使用 --ca--cafile 標誌。

bun install --cafile=/path/to/cafile
bun install --ca="..."

如果您正在使用現有的 .npmrc 檔案,您也可以在那裡設定 CA 憑證。

.npmrc
cafile=/path/to/cafile
ca="..."

bundleDependencies 支援

您現在可以在您的 package.json 中使用 bundleDependencies

package.json
{
  "bundleDependencies": ["is-even"]
}

這些是您預期已存在於您的 node_modules 資料夾中的依賴項,並且不會像其他依賴項一樣安裝。

bun add 尊重 package.json 縮排

我們修復了一個錯誤,該錯誤導致 bun add 不會尊重您的 package.json 中的間距和縮排。Bun 現在將保留您的 package.json 的縮排,無論它有多奇怪。

bun add is-odd
package.json
// an intentionally wacky package.json
{
  "dependencies": {
              "is-even": "1.0.0",
              "is-odd": "1.0.0"
  }
}

--omit=dev|optional|peer 支援

Bun 現在支援帶有 bun install--omit 標誌,這允許您省略 dev、optional 或 peer 依賴項。

bun install --omit=dev # omit dev dependencies
bun install --omit=optional # omit optional dependencies
bun install --omit=peer # omit peer dependencies
bun install --omit=dev --omit=optional # omit dev and optional dependencies

Bun 是一個測試執行器

Bun 有一個內建的測試執行器,可以輕鬆地在 JavaScript、TypeScript 和 JSX 中編寫和執行測試。它支援與 Jest 和 Vitest 相同的許多 API,包括 expect() 風格的 API。

在 Bun 1.2 中,我們對 bun test 進行了許多改進。

JUnit 支援

為了將 bun test 與 Jenkins、CircleCI 和 GitLab CI 等 CI/CD 工具一起使用,您可以使用 --reporter 選項將測試結果輸出到 JUnit XML 檔案。

bun test --reporter=junit --reporter-outfile=junit.xml
junit.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="bun test" tests="1" assertions="1" failures="1" time="0.001">
  <testsuite name="index.test.ts" tests="1" assertions="1" failures="1" time="0.001">
    <!-- ... -->
  </testsuite>
</testsuites>

您也可以透過將以下內容新增到您的 bunfig.toml 檔案來啟用 JUnit 報告。

bunfig.toml
[test.reporter]
junit = "junit.xml"

LCOV 支援

您可以使用 bun test --coverage 來產生文字式覆蓋率報告。

在 Bun 1.2 中,我們新增了對 LCOV 覆蓋率報告的支援。LCOV 是程式碼覆蓋率報告的標準格式,並被 Codecov 等許多工具使用。

bun test --coverage --coverage-reporter=lcov

預設情況下,這會在 coverage 目錄中輸出一個 lcov.info 覆蓋率報告檔案。您可以使用 --coverage-dir 更改覆蓋率目錄。

如果您想要始終啟用覆蓋率報告,您可以將以下內容新增到您的 bunfig.toml 檔案。

bunfig.toml
[test]
coverage = true
coverageReporter = ["lcov"]  # default ["text"]
coverageDir = "./path/to/folder"  # default "./coverage"

內聯快照

您現在可以使用 內聯快照,使用 expect().toMatchInlineSnapshot()

toMatchSnapshot() 將快照儲存在單獨的檔案中不同,toMatchInlineSnapshot() 將快照直接儲存在測試檔案中。這讓您更容易查看,甚至更改您的快照。

首先,編寫一個使用 toMatchInlineSnapshot() 的測試。

snapshot.test.ts
import { expect, test } from "bun:test";

test("toMatchInlineSnapshot()", () => {
  expect(new Date()).toMatchInlineSnapshot();
});

接下來,使用 bun test -u 更新快照,這是 --update-snapshots 的縮寫。

bun test -u

然後,瞧!Bun 已使用您的快照更新了測試檔案。

snapshot.test.ts
import { expect, test } from "bun:test";

test("toMatchInlineSnapshot()", () => {
   expect(new Date()).toMatchInlineSnapshot();
   expect(new Date()).toMatchInlineSnapshot(`2025-01-18T02:35:53.332Z`);
});

您也可以使用這些匹配器,它們的作用類似

test.only()

您可以使用 test.only() 來執行單個測試,排除所有其他測試。當您要除錯特定測試,並且不想執行整個測試套件時,這非常有用。

import { test } from "bun:test";

test.only("test a", () => {
  /* Only run this test  */
});

test("test b", () => {
  /* Don't run this test */
});

以前,為了讓這在 Bun 中運作,您必須使用 --only 標誌。

bun test --only

這很煩人,您通常會忘記這樣做,而像 Jest 這樣的測試執行器不需要它!在 Bun 1.2 中,我們讓這個功能「開箱即用」,無需標誌。

bun test

新的 expect() 匹配器

在 Bun 1.2 中,我們向 expect() API 新增了一系列匹配器。這些是與 Jest、Vitest 或 jest-extended 程式庫實作的相同的匹配器。

您可以使用 toContainValue() 及其衍生方法來檢查物件是否包含值。

const object = new Set(["bun", "node", "npm"]);

expect(object).toContainValue("bun");
expect(object).toContainValues(["bun", "node"]);
expect(object).toContainAllValues(["bun", "node", "npm"]);
expect(object).not.toContainAnyValues(["done"]);

或者,使用 toContainKey() 及其衍生方法來檢查物件是否包含鍵。

const object = new Map([
  ["bun", "1.2.0"],
  ["node", "22.13.0"],
  ["npm", "9.1.2"],
]);

expect(object).toContainKey("bun");
expect(object).toContainKeys(["bun", "node"]);
expect(object).toContainAllKeys(["bun", "node", "npm"]);
expect(object).not.toContainAnyKeys(["done"]);

您也可以使用 toHaveReturned() 及其衍生方法來檢查模擬函數是否已傳回值。

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

test("toHaveReturned()", () => {
  const mock = jest.fn(() => "foo");
  mock();
  expect(mock).toHaveReturned();
  mock();
  expect(mock).toHaveReturnedTimes(2);
});

自訂錯誤訊息

我們還新增了對使用 expect() 的自訂錯誤訊息的支援。

您現在可以將字串作為第二個參數傳遞給 expect(),它將用作錯誤訊息。當您想要記錄斷言正在檢查的內容時,這非常有用。

example.test.ts
import { test, expect } from 'bun:test';

test("custom error message", () => {
  expect(0.1 + 0.2).toBe(0.3);
  expect(0.1 + 0.2, "Floating point has precision error").toBe(0.3);
});
1 | import { test, expect } from 'bun:test';
2 |
3 | test("custom error message", () => {
4 |   expect(0.1 + 0.2, "Floating point has precision error").toBe(0.3);
                                                              ^
error: expect(received).toBe(expected)
error: Floating point has precision error

Expected: 0.3
Received: 0.30000000000000004

jest.setTimeout()

您現在可以使用 Jest 的 setTimeout() API 來更改目前作用域或模組中測試的預設逾時,而不是為每個測試設定逾時。

jest.setTimeout(60 * 1000); // 1 minute

test("do something that takes a long time", async () => {
  await Bun.sleep(Infinity);
});

您也可以從 Bun 的測試 API 匯入 setDefaultTimeout(),它的作用相同。我們選擇了不同的名稱,以避免與全域 setTimeout() 函數混淆。

import { setDefaultTimeout } from "bun:test";

setDefaultTimeout(60 * 1000); // 1 minute

Bun 是一個 JavaScript 打包器

Bun 是一個 JavaScript 和 TypeScript 打包器、轉譯器和壓縮器,可用於打包瀏覽器、Node.js 和其他平台的程式碼。

HTML 匯入

在 Bun 1.2 中,我們新增了對 HTML 匯入的支援。這讓您可以用單個 import 語句替換整個前端工具鏈。

若要開始使用,請將 HTML import 傳遞給 Bun.serve 中的 static 選項

import homepage from "./index.html";

Bun.serve({
  static: {
    "/": homepage,
  },

  async fetch(req) {
    // ... api requests
  },
});

當您向 / 發出請求時,Bun 會自動打包 HTML 檔案中的 <script><link> 標籤,將它們公開為靜態路由,並提供結果。

像這樣的 index.html 檔案

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>

會變成這樣

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>

若要深入了解 HTML 匯入及其運作方式,請查看 HTML 匯入 文件。

獨立可執行檔

您可以使用 bun build --compile 將您的應用程式和 Bun 編譯成獨立可執行檔。

在 Bun 1.2 中,我們新增了對跨平台編譯的支援。這讓您可以在 Linux 機器上建置 Windows 或 macOS 二進制檔案,反之亦然。

您可以在 macOS 或 Linux 機器上執行以下命令,它將編譯一個 Windows 二進制檔案。

bun build --compile --target=bun-windows-x64 app.ts
   [8ms]  bundle  1 modules
[1485ms] compile  app.exe bun-windows-x64-v1.2.0

對於 Windows 特定建置,您可以自訂圖示並隱藏主控台視窗。

bun build --compile --windows-icon=./icon.ico --windows-hide-console app.ts

位元組碼快取

您也可以使用 bun build --bytecode 標誌來產生位元組碼快取。這將像 eslint 這樣的應用程式的啟動時間縮短為 2 倍

bun build --bytecode --compile app.ts
./app
Hello, world!

您也可以在不使用 --compile 的情況下使用位元組碼快取。

bun build --bytecode --outdir=dist app.ts
ls dist
app.js  app.jsc

當 Bun 產生輸出檔案時,它也會產生 .jsc 檔案,其中包含其各自 .js 檔案的位元組碼快取。這兩個檔案都是執行所必需的,因為位元組碼編譯目前不編譯 async 函數、generator 或 eval。

位元組碼快取可能比原始碼大 8 倍,因此這以增加磁碟空間為代價加快了啟動速度。

CommonJS 輸出格式

您現在可以使用 bun build 將輸出格式設定為 CommonJS。以前,僅支援 ESM。

bun build --format=cjs app.ts

這讓建立適用於舊版本 Node.js 的程式庫和應用程式變得更加容易。

app.ts
app.js
app.ts
// app.ts
export default "Hello, world!";
app.js
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __moduleCache = /* @__PURE__ */ new WeakMap;
var __toCommonJS = (from) => {
  var entry = __moduleCache.get(from), desc;
  if (entry)
    return entry;
  entry = __defProp({}, "__esModule", { value: true });
  if (from && typeof from === "object" || typeof from === "function")
    __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
      get: () => from[key],
      enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
    }));
  __moduleCache.set(from, entry);
  return entry;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};

// app.js
var exports_site = {};
__export(exports_site, {
  default: () => site_default
});
module.exports = __toCommonJS(exports_site);
var site_default = "Hello, world!";

更好的 CommonJS 偵測

有些套件真的想要欺騙打包器並取得當前模組的檔案路徑、執行時 require 或檢查當前模組是否為主模組。它們嘗試各種方法使其運作,例如

"use strict";

if (eval("require.main") === eval("module.main")) {
  // ...
}

Bun 同時支援 CommonJS 和 ESM;事實上,您可以在同一個檔案中使用 require()import。但是,同時支援兩者的挑戰之一是存在很多歧義。

考慮以下程式碼,它是 CommonJS 還是 ESM?

console.log("123");

沒有辦法判斷。那麼,這個呢?

console.log(module.require("path"));

CommonJS,因為它正在使用 module.require() 來取得 path 模組。那這個呢?

import path from "path";
console.log(path);

ESM,因為它正在使用 import。但是,這個呢?

import path from "path";
const fs = require("fs");
console.log(fs.readFileSync(path.resolve("package.json"), "utf8"));

ESM,因為它正在使用 import。如果我們說它是 CommonJS 是因為 require,那麼 import 會破壞程式碼。我們想要簡化在 JavaScript 中建置內容,所以我們就說它是 ESM 而不要吹毛求疵。

最後,這個呢?

"use strict";

console.log(eval("module.require('path')"));

以前,Bun 會說 ESM,因為當沒有辦法判斷時(包括當檔案副檔名不明確時、package.json 中沒有「type」欄位時、沒有 export、沒有 import 等)它是預設值。

在 Bun 1.2 中,Bun 會說 CommonJS,因為檔案頂部有 "use strict" 指令。ESM 始終處於嚴格模式,因此顯式的 "use strict" 是多餘的。

此外,大多數輸出 CommonJS 的建置工具都在檔案頂部包含 "use strict"。因此,當檔案是 CommonJS 還是 ESM 完全不明確時,我們現在可以使用它作為最後的機會啟發式方法。

Plugin API

Bun 有一個通用的 plugin API,用於擴展打包器執行時。

您可以使用插件攔截 import() 語句、為 .yaml 等擴展名新增自訂載入器,並為 Bun 實作框架。

onBeforeParse()

在 Bun 1.2 中,我們為插件引入了一個新的生命週期掛鉤,onBeforeParse()

與執行 JavaScript 程式碼的現有生命週期掛鉤不同,此掛鉤必須是 N-API addon,它可以使用 Rust、C/C++ 或 Zig 等編譯語言實作。

掛鉤在解析之前立即呼叫,無需複製原始碼、無需經過字串轉換,並且幾乎沒有任何開銷。

例如,您可以建立一個 Rust 插件,將所有出現的 foo 替換為 bar

bun add -g @napi-rs/cli
napi new
cargo add bun-native-plugin

從那裡,您可以實作 onBeforeParse() 掛鉤。這些是進階 API,主要為想要使用原生程式碼使他們的插件真正快速的插件和框架作者設計。

lib.rs
build.ts
lib.rs
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader};
use napi_derive::napi;

define_bun_plugin!("foo-bar-plugin");

#[bun]
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
  let input_source_code = handle.input_source_code()?;
  let output_source_code = input_source_code.replace("foo", "bar");

  handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX);
  Ok(())
}
build.ts
import { build } from "bun";
import fooBarPlugin from "./foo-bar-plugin";

await build({
  entrypoints: ["./app.tsx"],
  plugins: [
    {
      name: "foo-bar-plugin",
      setup(build) {
        build.onBeforeParse(
          {
            namespace: "file",
            filter: "**/*.tsx",
          },
          {
            napiModule: fooBarPlugin,
            symbol: "replace_foo_with_bar",
          },
        );
      },
    },
  ],
});

其他變更

我們還對 bun buildBun.build() API 進行了許多其他改進。

注入環境變數

您現在可以將環境變數從您的系統環境注入到您的 bundle 中。

CLI
JavaScript
CLI
bun build --env="PUBLIC_*" app.tsx
JavaScript
import { build } from "bun";

await build({
  entrypoints: ["./app.tsx"],
  outdir: "./out",
  // Environment variables starting with "PUBLIC_"
  // will be injected in the build as process.env.PUBLIC_*
  env: "PUBLIC_*",
});

bun build --drop

您可以使用 --drop 從您的 JavaScript bundle 中移除函數呼叫。例如,如果您傳遞 --drop=console,所有對 console.log() 的呼叫都將從您的程式碼中移除。

JavaScript
CLI
JavaScript
import { build } from "bun";

await build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
  drop: ["console", "anyIdentifier.or.propertyAccess"],
});
CLI
bun build ./index.tsx --outdir ./out --drop=console --drop=anyIdentifier.or.propertyAccess

您現在可以使用 bun build 中的 banner 和 footer 選項在 bundle 上方或下方新增內容。

CLI
JavaScript
CLI
bun build --banner "/* Banner! */" --footer "/* Footer! */" app.ts
JavaScript
import { build } from "bun";

await build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  banner: "/* Banner! */",
  footer: "/* Footer! */",
});

這對於在 bundle 上方或下方附加內容(例如許可證或著作權聲明)非常有用。

/**
 * Banner!
 */
export default "Hello, world!";
/**
 * Footer!
 */

Bun.embeddedFiles()

您可以使用新的 Bun.embeddedFiles() API 來查看使用 bun build --compile 編譯的獨立可執行檔中所有嵌入式檔案的列表。

import { embeddedFiles } from "bun";

for (const file of embeddedFiles) {
  console.log(file.name); // "logo.png"
  console.log(file.size); // 1234
  console.log(await file.bytes()); // Uint8Array(1234) [...]
}

require.main === module

以前,使用 require.main === module 會將模組標記為 CommonJS。現在,Bun 會將其重寫為 import.meta.main,這意味著您可以將此模式與 import 語句一起使用。

import * as fs from "fs";

if (typeof require !== "undefined" && require.main === module) {
  console.log("main!", fs);
}

--ignore-dce-annotations

某些 JavaScript 工具支援特殊的註解,這些註解可以在 dead-code elimination 期間影響行為。例如,@__PURE__ 註解告訴打包器函數呼叫是純粹的(無論它是否真的是純粹的),並且如果未使用該呼叫,則可以移除該呼叫。

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

有時,程式庫可能包含不正確的註解,這可能會導致 Bun 移除所需的副作用。

若要解決這些問題,您可以在執行 bun build 時使用 --ignore-dce-annotations 標誌來忽略所有註解。只有當 dead-code elimination 破壞 bundle 時才應使用此選項,並且應優先修正註解,而不是保持此標誌開啟。

--packages=external

您現在可以控制套件依賴項是否包含在您的 bundle 中。如果 import 不是以 .../ 開頭,則它被視為套件。

CLI
JavaScript
CLI
bun build ./index.ts --packages external
JavaScript
await Bun.build({
  entrypoints: ["./index.ts"],
  packages: "external",
});

這在打包程式庫時非常有用。它讓您可以減少使用者必須下載的檔案數量,同時繼續支援 peer 或外部依賴項。

內建 CSS 解析器

在 Bun 1.2 中,我們在 Bun 中實作了一個新的 CSS 解析器和打包器。

它源自 LightningCSS 的出色工作,並從 Rust 重寫為 Zig,以便它可以與 Bun 的自訂 JavaScript 和 TypeScript 解析器、打包器和執行時整合。

Bun 是一個用於執行和建置 JavaScript 和 TypeScript 的完整工具組。Bun 的內建 JavaScript 打包器 bun build 缺少的功能之一是支援打包和壓縮 CSS。

運作方式

CSS 打包器結合了多個 CSS 檔案和使用 url@import@font-face 等指令引用的資源,成為您可以發送到瀏覽器的單個 CSS 檔案,避免了網路請求的瀑布。

index.css
foo.css
bar.css
index.css
@import "foo.css";
@import "bar.css";
foo.css
.foo {
  background: red;
}
bar.css
.bar {
  background: blue;
}

若要查看它的運作方式,您可以嘗試使用 bun build

bun build ./index.css

您將看到 CSS 檔案如何組合到單個 CSS 檔案中。

dist.css
/** foo.css */
.foo {
  background: red;
}

/** bar.css */
.bar {
  background: blue;
}

從 JavaScript 匯入 .css 檔案

我們也讓您可以從您的 JavaScript 和 TypeScript 程式碼中匯入 .css 檔案。這將建立一個額外的 CSS 入口點,它將組合從 JavaScript 模組圖匯入的所有 CSS 檔案以及 @import 規則。

index.ts
import "./style.css";
import MyComponent from "./MyComponent.tsx";

// ... rest of your app

在此範例中,如果 MyComponent.tsx 匯入了另一個 CSS 檔案,則不是將額外的 .css 檔案新增到 bundle,而是將每個入口點匯入的所有 CSS 都展平為單個 CSS 檔案。

shell
bun build ./index.ts --outdir=dist
  index.js     0.10 KB
  index.css    0.10 KB
[5ms] bundle 4 modules

使用 Bun.build()

您也可以使用程式化的 Bun.build() API 來打包 CSS。這讓您可以使用相同的 API 在同一個建置中打包 CSS 和 JavaScript。

api.ts
import { build } from "bun";

const results = await build({
  entrypoints: ["./index.css"],
  outdir: "./dist",
});

console.log(results);

Bun API

除了支援 Node.js 和 Web API 之外,Bun 還擁有不斷成長的標準程式庫,讓您可以輕鬆完成常見任務,而無需新增更多外部依賴項。

Bun.serve() 中的靜態路由

Bun 具有內建的 HTTP 伺服器,可讓您使用標準 API(例如 RequestResponse)輕鬆回應 HTTP 請求。在 Bun 1.2 中,我們使用新的 static 屬性新增了對靜態路由的支援。

若要定義靜態路由,請將請求路徑作為鍵傳遞,並將 Response 物件作為值傳遞。

import { serve } from "bun";

serve({
  static: {
    "/health-check": new Response("Ok!"),
    "/old-link": Response.redirect("/new-link", 301),
    "/api/version": Response.json(
      {
        app: require("./package.json").version,
        bun: Bun.version,
      },
      {
        headers: { "X-Powered-By": "bun" },
      },
    ),
  },
  async fetch(request) {
    return new Response("Dynamic!");
  },
});

靜態路由比在 fetch() 處理器中自行執行快達 40%。回應主體、標頭和狀態碼都快取在記憶體中,因此沒有 JavaScript 分配或垃圾回收。

如果您想要重新載入靜態路由,您可以使用 reload() 方法。如果您想要定期更新靜態路由,或在檔案變更時更新靜態路由,這非常有用。

import { serve } from "bun";

const server = serve({
  static: {
    "/": new Response("Static!"),
  },
  async fetch(request) {
    return new Response("Dynamic!");
  },
});

setInterval(() => {
  const date = new Date().toISOString();
  server.reload({
    static: {
      "/": new Response(`Static! Updated at ${date}`),
    },
  });
}, 1000);

Bun.udpSocket()

雖然我們在 Bun 1.2 中新增了對 node:dgram 的支援,但我們也在 Bun 的 API 中引入了 UDP socket 支援。Bun.udpSocket() 是一種更快、更現代的替代方案,並且類似於現有的 Bun.listen() API。

import { udpSocket } from "bun";

const server = await udpSocket({
  socket: {
    data(socket, data, port, addr) {
      console.log(`Received data from ${addr}:${port}:`, data.toString());
    },
  },
});

const client = await udpSocket({ port: 0 });
client.send("Hello!", server.port, "127.0.0.1");

Bun 的 UDP socket API 專為效能而建置。與 Node.js 不同,它可以使用單個 syscall 發送多個 UDP datagram,並支援回應來自作業系統的反壓。

const socket = await Bun.udpSocket({
  port: 0,
  socket: {
    drain(socket) {
      // Socket is no longer under backpressure
    },
  },
});

// Send multiple UDP datagrams with a single syscall:
// [ <data>, <port>, <address> ][]
socket.sendMany([
  ["Hello", 12345, "127.0.0.1"],
  ["from", 12346, "127.0.0.1"],
  ["Bun 1.2", 12347, "127.0.0.1"],
]);

這非常適合建置需要將遊戲狀態更新廣播到每個 peer 的遊戲伺服器。

Bun.file()

Bun 具有內建的 Bun.file() API,可讓您輕鬆讀取和寫入檔案。它擴展了 Web 標準 Blob API,並使在伺服器環境中處理檔案變得更容易。

在 Bun 1.2 中,我們為 Bun.file() API 新增了更多支援。

delete()

您現在可以使用 delete() 方法刪除檔案。也支援 unlink() 的別名。

import { file } from "bun";

await file("./package.json").delete();
await file("./node_modules").unlink();

stat()

您現在可以使用 stat() 方法取得檔案的元數據。這會傳回與 Node.js 中 fs.stat() 相同的 Stats 物件。

import { file } from "bun";

const stat = await file("./package.json").stat();
console.log(stat.size); // => 1024
console.log(stat.mode); // => 33206
console.log(stat.isFile()); // => true
console.log(stat.isDirectory()); // => false
console.log(stat.ctime); // => 2025-01-21T16:00:00+00:00

支援 S3 檔案

憑藉新加入的對 S3 的內建支援,您可以將相同的 Bun.file() API 與 S3 檔案一起使用。

import { s3 } from "bun";

const stat = await s3("s3://folder/my-file.txt").stat();
console.log(stat.size); // => 1024
console.log(stat.type); // => "text/plain;charset=utf-8"

await s3("s3://folder/").unlink();

Bun.color()

為了使用 bun build 支援 CSS,我們在 Bun 1.2 中實作了自己的 CSS 解析器。在進行這項工作時,我們決定公開一些用於處理顏色的有用 API。

您可以使用 Bun.color() 來解析、正規化和將顏色轉換為各種格式。它支援 CSS、ANSI 顏色代碼、RGB、HSL 等。

import { color } from "bun";

color("#ff0000", "css"); // => "red"
color("rgb(255, 0, 0)", "css"); // => "red"
color("red", "ansi"); // => "\x1b[31m"
color("#f00", "ansi-16m"); // => "\x1b[38;2;255;0;0m"
color(0xff0000, "ansi-256"); // => "\u001b[38;5;196m"
color({ r: 255, g: 0, b: 0 }, "number"); // => 16711680
color("hsl(0, 0%, 50%)", "{rgba}"); // => { r: 128, g: 128, b: 128, a: 1 }

dns.prefetch()

您可以使用新的 dns.prefetch() API 在需要之前預先取得 DNS 記錄。如果您想要在啟動時預熱 DNS 快取,這非常有用。

import { dns } from "bun";

// ...on startup
dns.prefetch("example.com");

// ...later on
await fetch("https://example.com/");

這將預先取得 example.com 的 DNS 記錄,並使其可用於 fetch() 請求。您也可以使用 dns.getCacheStats() API 來觀察 DNS 快取。

import { dns } from "bun";

await fetch("https://example.com/");

console.log(dns.getCacheStats());
// {
//   cacheHitsCompleted: 0,
//   cacheHitsInflight: 0,
//   cacheMisses: 1,
//   size: 1,
//   errors: 0,
//   totalCount: 1,
// }

實用工具

我們還向 Bun 的 API 新增了一些隨機實用工具。

Bun.inspect.table()

您現在可以使用 Bun.inspect.table() 將表格資料格式化為字串。它類似於 console.table,但它會傳回字串而不是列印到主控台。

console.log(
  Bun.inspect.table([
    { a: 1, b: 2, c: 3 },
    { a: 4, b: 5, c: 6 },
    { a: 7, b: 8, c: 9 },
  ]),
);

// ┌───┬───┬───┬───┐
// │   │ a │ b │ c │
// ├───┼───┼───┼───┤
// │ 0 │ 1 │ 2 │ 3 │
// │ 1 │ 4 │ 5 │ 6 │
// │ 2 │ 7 │ 8 │ 9 │
// └───┴───┴───┴───┘

Bun.randomUUIDv7()

您可以使用 Bun.randomUUIDv7() 來產生 UUID v7,這是一種適用於排序和資料庫的單調 UUID。

index.ts
import { randomUUIDv7 } from "bun";

const uuid = randomUUIDv7();
// => "0192ce11-26d5-7dc3-9305-1426de888c5a"

Bun 內建 SQLite 用戶端的新功能

Bun 內建了 SQLite 用戶端,讓查詢 SQLite 資料庫變得容易。在 Bun 1.2 中,我們新增了一些新功能,使其更易於使用。

無 ORM 物件映射

當您查詢 SQL 資料庫時,通常會希望將查詢結果映射到 JavaScript 物件。這就是為什麼有這麼多流行的 ORM (物件關聯映射) 套件,例如 Prisma 和 TypeORM。

您現在可以使用 query.as(Class) 將查詢結果映射到類別的實例。這讓您無需使用 ORM 即可附加方法、getter 和 setter。

import { Database } from "bun:sqlite";

class Tweet {
  id: number;
  text: string;
  username: string;

  get isMe() {
    return this.username === "jarredsumner";
  }
}

const db = new Database("tweets.db");
const tweets = db.query("SELECT * FROM tweets").as(Tweet);

for (const tweet of tweets.all()) {
  if (!tweet.isMe) {
    console.log(`${tweet.username}: ${tweet.text}`);
  }
}

基於效能考量,不支援類別建構子、預設初始化器和私有欄位。相反地,它使用相當於 Object.create() 的方法來建立一個以類別原型為基礎的新物件,並將資料列的值指派給它。

同樣重要的是要注意,這不是 ORM。它不管理關聯性、產生 SQL 查詢或任何類似的功能。但是,它確實減少了從 SQLite 取得 JavaScript 物件的大量樣板程式碼!

可迭代查詢

您現在可以使用 query.iterate() 來取得一個迭代器,該迭代器會隨著資料庫傳回資料列而產生資料列。當您想要一次處理一個資料列,而無需將它們全部載入記憶體時,這非常有用。

import { Database } from "bun:sqlite";

class User {
  id: number;
  email: string;
}

const db = new Database("users.db");
const rows = db.query("SELECT * FROM users").as(User).iterate();

for (const row of rows) {
  console.log(row);
}

您也可以使用 for 迴圈迭代查詢,而無需呼叫 iterate()

for (const row of db.query("SELECT * FROM users")) {
  console.log(row); // { id: 1, email: "hello@bun.sh" }
}

嚴格查詢參數

現在,當您傳遞 JavaScript 值作為查詢參數時,可以省略 $@: 前綴。

import { Database } from "bun:sqlite";

const db = new Database(":memory:", {
  strict: false,
  strict: true,
});

const query = db.query(`select $message;`);

query.all({
  $message: "Hello world"
  message: "Hello world"
});

若要使用此行為,請啟用 strict 選項。這將允許您省略 $@: 前綴,並且如果缺少參數,將會拋出錯誤。

追蹤已變更的資料列

現在,在執行查詢時,您可以存取已變更的資料列數和最後插入的資料列 ID。

import { Database } from "bun:sqlite";

const db = new Database(":memory:");
db.run(`CREATE TABLE users (id INTEGER, username TEXT)`);

const { changes, lastInsertRowid } = db.run(
  `INSERT INTO users VALUES (1, 'jarredsumner')`,
);

console.log({
  changes, // => 1
  lastInsertRowid, // => 1
});

BigInt 支援

如果您想要使用 64 位元整數,可以啟用 safeIntegers 選項。這會將整數作為 BigInt 傳回,而不是截斷的 number

import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
const query = db.query(
  `SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 1n} as maxInteger`,
);

const { maxInteger } = query.get();
console.log(maxInteger); // => 9007199254740992n

您也可以使用 safeIntegers() 方法在每次查詢的基礎上啟用此功能。

import { Database } from "bun:sqlite";

const db = new Database(":memory:", { strict: true });
const query = db.query("SELECT $value as value").safeIntegers(true);

const { value } = query.get({
  value: BigInt(Number.MAX_SAFE_INTEGER) + 1n,
});
console.log(value); // => 9007199254740992n

使用 using 進行可靠的清除

透過 JavaScript 的 using 語法,您可以在陳述式和資料庫的變數超出範圍時自動關閉它們。即使拋出錯誤,這也允許您清除資料庫資源。繼續閱讀以瞭解更多關於 Bun 對這項新 JavaScript 功能的支援細節。

import { Database } from "bun:sqlite";

{
  using db = new Database("file.db");
  using query = db.query("SELECT * FROM users");
  for (const row of query.all()) {
    throw new Error("Oops!"); // no try/catch block needed!
  }
}

// scope ends here, so `db` and `query` are automatically closed

從 JavaScript 編譯並執行 C 程式碼

我們新增了從 JavaScript 編譯和執行 C 程式碼的實驗性支援。這是一種從 JavaScript 使用 C 系統函式庫的簡單方法,無需建置步驟

random.c
random.ts
random.c
#include <stdio.h>
#include <stdlib.h>

int random() {
  return rand() + 42;
}
random.ts
import { cc } from "bun:ffi";

const { symbols: { random } } = cc({
  source: "./random.c",
  symbols: {
    random: {
      returns: "int",
      args: [],
    },
  },
});

console.log(random()); // 42

這有什麼用處?

對於進階用例或效能至關重要的情況,您有時需要從 JavaScript 使用系統函式庫。如今,最常見的做法是使用 node-gyp 編譯 N-API 附加元件。如果套件使用此方法,您可能會注意到,因為它會在您安裝時執行 postinstall 指令碼。

但是,這不是一個很好的體驗。您的系統需要現代版本的 Python 和 C 編譯器,通常使用類似 apt install build-essential 的指令安裝。

而且希望您不會遇到編譯器或 node-gyp 錯誤,這可能會非常令人沮喪。

gyp ERR! command "/usr/bin/node" "/tmp/node-gyp@latest--bunx/node_modules/.bin/node-gyp" "configure" "build"
gyp ERR! cwd /bun/test/node_modules/bktree-fast
gyp ERR! node -v v12.22.9
gyp ERR! node-gyp -v v9.4.0
gyp ERR! Node-gyp failed to build your package.
gyp ERR! Try to update npm and/or node-gyp and if it does not help file an issue with the package author.
error: "node-gyp" exited with code 7 (SIGBUS)

它是如何運作的?

如果您不知道,Bun 內嵌了一個名為 tinycc 的內建 C 編譯器。驚喜吧!

與傳統的 C 編譯器 (如 gccclang) 不同,它們可能需要幾秒鐘才能編譯一個簡單的程式,tinycc 可以在幾毫秒內編譯簡單的 C 程式碼。這使得 Bun 可以在需要時編譯您的 C 程式碼,而無需建置步驟。

使用 bun:ffi API,您可以從 JavaScript 編譯和執行 C 程式碼。這是一個範例專案,它使用 N-API 從 C 程式碼傳回 JavaScript 字串。

hello-napi.c
hello-napi.js
hello-napi.c
#include <node/node_api.h>

napi_value hello_napi(napi_env env) {
  napi_value result;
  napi_create_string_utf8(env, "Hello, N-API!", NAPI_AUTO_LENGTH, &result);
  return result;
}
hello-napi.js
import { cc } from "bun:ffi";
import source from "./hello-napi.c" with { type: "file" };

const hello = cc({
  source,
  symbols: {
    hello_napi: {
      args: ["napi_env"],
      returns: "napi_value",
    },
  },
});

console.log(hello());
// => "Hello, N-API!"

只要您有 Bun,這就可以直接運作,而無需使用 node-gyp 進行建置步驟。

musl 支援

在 Bun 1.2 中,我們推出了一個新的 Bun 建置版本,它可以在使用 musl libc 而不是 glibc 的 Linux 發行版上運作,例如 Alpine Linux。Linux x64 和 aarch64 都支援此版本。

您也可以在 Docker 中使用 alpine 版本 的 Bun。

docker run --rm -it oven/bun:alpine bun --print 'Bun.file("/etc/alpine-release").text()'
3.20.5

雖然 musl 可以實現更小的容器映像檔,但它的效能往往比 glibc 版本的 Bun 稍慢。除非您有使用 musl 的特殊原因,否則我們建議使用 glibc。

JavaScript 功能

JavaScript 是一種不斷發展的語言。在 Bun 1.2 中,由於 TC39 委員會的合作以及 WebKit 團隊的努力,更多 JavaScript 功能可用。

匯入屬性

現在,您可以在匯入檔案時指定匯入屬性。當您想要匯入非 JavaScript 程式碼的檔案 (例如 JSON 物件或文字檔) 時,這非常有用。

import json from "./package.json" with { type: "json" };
typeof json; // "object"

import html from "./index.html" with { type: "text" };
typeof html; // "string"

import toml from "./bunfig.toml" with { type: "toml" };
typeof toml; // "object"

您也可以使用 import() 指定匯入屬性。

const { default: json } = await import("./package.json", {
  with: { type: "json" },
});
typeof json; // "object"

使用 using 進行資源管理

透過 JavaScript 中新引入的 using 語法,您可以在變數超出範圍時自動關閉資源。

現在,您可以使用 using 定義變數,而不是使用 letconst 定義變數。

import { serve } from "bun";

{
  using server = serve({
    port: 0,
    fetch(request) {
      return new Response("Hello, world!");
    },
  });

  doStuff(server);
}

function doStuff(server) {
  // ...
}

在此範例中,即使拋出例外,伺服器也會在 server 變數超出範圍時自動關閉。這對於確保資源得到正確清理非常有用,尤其是在測試中。

為了支援此功能,物件的原型必須定義 [Symbol.dispose] 方法,如果是非同步資源,則必須定義 [Symbol.asyncDispose] 方法。

class Resource {
  [Symbol.dispose]() { /* ... */ }
}

using resource = new Resource();

class AsyncResource {
  async [Symbol.asyncDispose]() { /* ... */ }
}

await using asyncResource = new AsyncResource();

我們還在數十個 Bun API 中新增了對 using 的支援,包括 Bun.spawn()Bun.serve()Bun.connect()Bun.listen()bun:sqlite

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

test("able to spawn a process", async () => {
  using subprocess = spawn({
    cmd: [process.execPath, "-e", "console.log('Hello, world!')"],
    stdout: "pipe",
  });

  // Even if this expectation fails, the subprocess will still be closed.
  const stdout = new Response(subprocess.stdout).text();
  await expect(stdout).resolves.toBe("Hello, world!");
});

Promise.withResolvers()

您可以使用 Promise.withResolvers() 來建立一個 Promise,當您呼叫 resolvereject 函數時,該 Promise 會解析或拒絕。

const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve(), 1000);
await promise;

這是 new Promise() 的一個有用的替代方案,因為您不需要建立新的作用域。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve(), 1000);
});
await promise;

Promise.try()

您可以使用 Promise.try() 來建立一個 Promise,它封裝了一個同步或非同步函數。

const syncFn = () => 1 + 1;
const asyncFn = async (a, b) => 1 + a + b;

await Promise.try(syncFn); // => 2
await Promise.try(asyncFn, 2, 3); // => 6

如果您不知道函數是同步還是非同步的,這非常有用。以前,您必須執行類似這樣的操作

await new Promise((resolve) => resolve(syncFn()));
await new Promise((resolve) => resolve(asyncFn(2, 3)));

Error.isError()

您現在可以使用 Error.isError() 來檢查物件是否為 Error 實例。

Error.isError(new Error()); // => true
Error.isError({}); // => false
Error.isError(new (class Error {})()); // => false
Error.isError({ [Symbol.toStringTag]: "Error" }); // => false

這比使用 instanceof 更正確,因為原型鏈可能會被竄改,而且當使用 node:vm 時,instanceof 可能會傳回誤判。

import { runInNewContext } from "node:vm";
const crossRealmError = runInNewContext("new Error()");

crossRealmError instanceof Error; // => false
Error.isError(crossRealmError); // => true

Uint8Array.toBase64()

您現在可以使用 Uint8Array 編碼和解碼 base64 字串。

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

這些 API 是 Node.js 中使用 Buffer.toString("base64") 的標準替代方案。

Uint8Array.toHex()

您也可以將 Uint8Array 轉換為十六進位字串,以及從十六進位字串轉換為 Uint8Array

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

這些 API 是 Node.js 中使用 Buffer.toString("hex") 的標準替代方案。

迭代器輔助方法

有一些新的 API 可以更輕鬆地使用 JavaScript 迭代器和產生器。

iterator.map(fn)

傳回一個迭代器,該迭代器產生應用於原始迭代器每個值的 fn 函數的結果,類似於 Array.prototype.map

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(3, 5).map((x) => x * 2);
result.next(); // { value: 6, done: false }

iterator.flatMap(fn)

傳回一個迭代器,該迭代器產生原始迭代器的值,但會展平 fn 函數的結果,類似於 Array.prototype.flatMap

function* randomThoughts(): Generator<string> {
  yield "Bun is written in Zig";
  yield "Bun runs JavaScript and TypeScript";
}

const result = randomThoughts().flatMap((x) => x.split(" "));
result.next(); // { value: "Bun", done: false }
result.next(); // { value: "is", done: false }
// ...
result.next(); // { value: "TypeScript", done: false }

iterator.filter(fn)

傳回一個迭代器,該迭代器僅產生通過 fn 述詞的值,類似於 Array.prototype.filter

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(3, 5).filter((x) => x % 2 === 0);
result.next(); // { value: 4, done: false }

iterator.take(n)

傳回一個迭代器,該迭代器產生原始迭代器的前 n 個值。

function* odds(): Generator<number> {
  let i = 1;
  while (true) {
    yield i;
    i += 2;
  }
}

const result = odds().take(1);
result.next(); // { value: 1, done: false }
result.next(); // { done: true }

iterator.drop(n)

傳回一個迭代器,該迭代器產生原始迭代器的所有值,但排除前 n 個值。

function* evens(): Generator<number> {
  let i = 0;
  while (true) {
    yield i;
    i += 2;
  }
}

const result = evens().drop(2);
result.next(); // { value: 4, done: false }
result.next(); // { value: 6, done: false }

iterator.reduce(fn, initialValue)

使用函數縮減迭代器的值,類似於 Array.prototype.reduce

function* powersOfTwo(): Generator<number> {
  let i = 1;
  while (true) {
    yield i;
    i *= 2;
  }
}

const result = powersOfTwo()
  .take(5)
  .reduce((acc, x) => acc + x, 0);
console.log(result); // 15

iterator.toArray()

傳回一個陣列,其中包含原始迭代器的所有值。請確保迭代器是有限的,否則這將導致無限迴圈。

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(1, 5).toArray();
console.log(result); // [1, 2, 3, 4]

iterator.forEach(fn)

在原始迭代器的每個值上呼叫 fn 函數,類似於 Array.prototype.forEach

function* randomThoughts(): Generator<string> {
  yield "Bun is written in Zig";
  yield "Bun runs JavaScript and TypeScript";
}

const result = randomThoughts().forEach((x) => console.log(x));
// Bun is written in Zig
// Bun runs JavaScript and TypeScript

iterator.find(fn)

傳回原始迭代器中第一個通過 fn 述詞的值,類似於 Array.prototype.find。如果不存在這樣的值,則傳回 undefined

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(0, 99).find((x) => x % 100 === 0);
console.log(result); // undefined

Float16Array

現在支援使用 Float16Array 的 16 位元浮點陣列。雖然 16 位元浮點數的精確度不如 32 位元浮點數,但它們的記憶體效率更高。

const float16 = new Float16Array(3);
const float32 = new Float32Array(3);

for (let i = 0; i < 3; i++) {
  float16[i] = i + 0.123;
  float32[i] = i + 0.123;
}

console.log(float16); // Float16Array(3) [ 0, 1.123046875, 2.123046875 ]
console.log(float32); // Float32Array(3) [ 0, 1.1230000257492065, 2.122999906539917 ]

Web API

除了新的 JavaScript 功能外,Bun 中還有新的 Web 標準 API 可供您使用。

TextDecoderStream

您現在可以使用 TextDecoderStreamTextEncoderStream 來編碼和解碼資料流。這些 API 是 TextDecoderTextEncoder 的串流等效項目。

您可以使用 TextDecoderStream 將位元組流解碼為 UTF-8 字串流。

const response = await fetch("https://example.com");
const body = response.body.pipeThrough(new TextDecoderStream());

for await (const chunk of body) {
  console.log(chunk); // typeof chunk === "string"
}

或者您可以使用 TextEncoderStream 將 UTF-8 字串流編碼為位元組流。在 Bun 中,這比 Node.js 快 30 倍

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello, world!");
    controller.close();
  },
});
const body = stream.pipeThrough(new TextEncoderStream());

for await (const chunk of body) {
  console.log(chunk); // chunk instanceof Uint8Array
}

具有 stream 選項的 TextDecoder

TextDecoder 也支援 stream 選項。這會告知解碼器區塊是較大串流的一部分,如果區塊不是完整的 UTF-8 程式碼點,則不應拋出錯誤。

const decoder = new TextDecoder("utf-8");
const first = decoder.decode(new Uint8Array([226, 153]), { stream: true });
const second = decoder.decode(new Uint8Array([165]), { stream: true });

console.log(first); // ""
console.log(second); // "♥"

bytes() API

您現在可以在串流上使用 bytes() 方法,該方法會傳回串流資料的 Uint8Array

const response = await fetch("https://example.com/");
const bytes = await response.bytes();
console.log(bytes); // Uint8Array(1256) [ 60, 33, ... ]

以前,您必須使用 arrayBuffer(),然後建立新的 Uint8Array

const blob = new Blob(["Hello, world!"]);
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);

包括 ResponseBlobBun.file() 在內的多個 API 都支援 bytes() 方法。

import { file } from "bun";

const content = await file("./hello.txt").bytes();
console.log(content); // Uint8Array(1256) [ 60, 33, ... ]

串流 fetch() 上傳

您現在可以使用串流主體傳送 fetch() 請求。這對於上傳大型檔案或內容長度事先未知的資料流非常有用。

await fetch("https://example.com/upload", {
  method: "POST",
  body: async function* () {
    yield "Hello";
    yield " ";
    yield "world!";
  },
});

console.group()

您現在可以使用 console.group()console.groupEnd() 來建立巢狀記錄訊息。以前,這些在 Bun 中未實作,並且不會執行任何操作。

index.js
console.group("begin");
console.log("indent!");
console.groupEnd();
// begin
//   indent!

URL.createObjectURL()

現在支援 URL.createObjectURL(),它會從 Blob 物件建立 URL。然後,這些 URL 可以用於諸如 fetch()Workerimport() 之類的 API 中。

Worker 結合使用時,它允許一種簡單的方式來產生額外的執行緒,而無需為 Worker 的指令碼建立新的獨立 URL。由於 Worker 指令碼也透過 Bun 的轉譯器執行,因此支援 TypeScript 語法。

worker.ts
const code = `
  const foo: number = 123;
  postMessage({ foo } satisfies Data);
`;
const blob = new File([code], "worker.ts");
const url = URL.createObjectURL(blob);

const worker = new Worker(url);
worker.onmessage = ({ data }) => {
  console.log("Received data:", data);
};

AbortSignal.any()

您可以使用 AbortSignal.any() 來組合多個 AbortSignal 實例。如果其中一個子訊號中止,則父訊號也會中止。

const { signal: firstSignal } = new AbortController();
fetch("https://example.com/", { signal: firstSignal });

const { signal: secondSignal } = new AbortController();
fetch("https://example.com/", { signal: secondSignal });

// Cancels if either `firstSignal` or `secondSignal` is aborted
const signal = AbortSignal.any([firstSignal, secondSignal]);
await fetch("https://example.com/slow", { signal });

行為變更

Bun 1.2 包含一些您應該注意的行為調整,但我們認為不太可能破壞您的程式碼。除非我們認為現狀非常糟糕,值得做出這些變更,否則我們會避免進行這些變更。

bun run 使用正確的目錄

以前,當您使用 bun run 執行 package.json 指令碼時,指令碼的工作目錄與 shell 的目前工作目錄相同。

在大多數情況下,您不會注意到差異,因為 shell 的工作目錄通常package.json 檔案的父目錄相同。

cd /path/to/project
ls
package.json
bun run pwd
/path/to/project

但是,如果您 cd 到不同的目錄,您會注意到差異。

cd dist
bun run pwd
/path/to/project/dist

這與其他套件管理器 (如 npmyarn) 的行為不符,並且往往會導致意外行為。

在 Bun 1.2 中,指令碼的工作目錄現在是 package.json 檔案的父目錄,而不是 shell 的目前工作目錄。

cd /path/to/project/dist
bun run pwd
/path/to/project/dist
/path/to/project

bun test 中未捕獲的錯誤

以前,當測試案例之間存在未捕獲的錯誤或拒絕時,bun test 不會失敗。

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

test("should have failed, but didn't", () => {
  setTimeout(() => {
    throw new Error("Oops!");
  }, 1);
});

在 Bun 1.2 中,此問題已修正,bun test 將會報告失敗。

# Unhandled error between tests
-------------------------------
1 | import { test, expect } from "bun:test";
2 |
3 | test("should have failed, but didn't", () => {
4 |   setTimeout(() => {
5 |     throw new Error("Oops!");
              ^
error: Oops!
      at foo.test.ts:5:11
-------------------------------

server.stop() 傳回 Promise

以前,沒有辦法優雅地等待 Bun HTTP 伺服器關閉連線。

為了實現這一點,我們讓 stop() 傳回一個 Promise,該 Promise 在處理中的 HTTP 連線關閉時解析。

interface Server {
   stop(): void;
   stop(): Promise<void>;
}

Bun.build() 在失敗時拒絕

以前,當 Bun.build() 失敗時,它會在 logs 陣列中報告錯誤。這通常令人困惑,因為 Promise 會成功解析。

import { build } from "bun";

const result = await build({
  entrypoints: ["./bad.ts"],
});

console.log(result.logs[0]); // error: ModuleNotFound resolving "./bad.ts" (entry point)

在 Bun 1.2 中,當 Bun.build() 失敗時,現在會拒絕,而不是在 logs 陣列中傳回錯誤。

const result = build({
  entrypoints: ["./bad.ts"],
});

await result; // error: ModuleNotFound resolving "./bad.ts" (entry point)

如果您想要還原到舊的行為,可以設定 throw: false 選項。

const result = await build({
  entrypoints: ["./bad.ts"],
  throw: false,
});

bun -pbun --print 的別名

以前,bun -pbun --port 的別名,用於變更 Bun.serve() 的埠。此別名是在 Bun 支援 bun --print 之前新增的。

為了與 Node.js 匹配,我們已將 bun -p 變更為 bun --print 的別名。

bun -p 'new Date()'
2025-01-17T22:55:27.659Z

bun build --sourcemap

以前,使用 bun build --sourcemap 會預設為內嵌來源地圖。

bun build --sourcemap ./index.ts --outfile ./index.js
index.js
console.log("Hello Bun!");
//# sourceMappingURL=data:application/json;base64,...

這令人困惑,因為它與其他工具 (如 esbuild) 的行為相反。

在 Bun 1.2 中,bun build --sourcemap 現在預設為 linked 來源地圖。

index.js
index.js.map
index.js
console.log("Hello Bun!");
index.js.map
{
  "version": 3,
  "sources": ["index.ts"],
  // ...
}

如果您想要還原到舊的行為,可以使用 --sourcemap=inline

Bun 變得更快了

我們花費大量時間來改進 Bun 的效能。我們幾乎每天都會發布「在下一個版本的 Bun 中」的每日更新,您可以在 @bunjavascript 上關注。

以下是我們在 Bun 1.2 中進行的一些效能改進的預覽。

node:http2 快了 2 倍
node:http 在上傳到 S3 時快了 5 倍

不要與 Bun 內建的 S3 用戶端混淆,後者甚至快了 5 倍。

path.resolve() 快了 30 倍
fetch() 在 DNS 解析時快了 2 倍
bun --hot 使用的記憶體減少了 2 倍
fs.readdirSync() 在 macOS 上快了 5%
String.at() 快了 44%
atob() 快了 8 倍

對於大型字串輸入,atob() 最多快了 8 倍。

fetch() 解壓縮速度快了 30%
Buffer.from(String, "base64") 快了 30 倍

對於大型字串輸入,Buffer.from(string, "base64") 最多快了 30 倍。

JSON.parse() 最多快了 4 倍

對於大型字串輸入,JSON.parse() 快了 2 倍到 4 倍。
對於物件輸入,快了 6%。

Bun.serve() 的吞吐量增加了 2 倍

在存取請求 body 後,request.json() 和類似方法的快速路徑現在可以運作。這使得某些 Bun.serve() 應用程式的吞吐量最多快了 2 倍。

Error.captureStackTrace() 快了 9 倍
fs.readFile() 快了 10%

對於小型檔案,fs.readFile() 最多快了 10%。

console.log(String) 快了 50%

當您將字串作為引數與 console.log() 一起使用時,它現在快了 50%。

JavaScript 在 Windows 上更快

在 Bun 1.2 中,我們在 Windows 上啟用了 JIT。以前,JIT 僅適用於 macOS 和 Linux。

JIT 或即時編譯是一種在執行階段編譯程式碼的技術,而不是在預先編譯。這使得 JavaScript 更快,但實作起來也更複雜。

現在,JavaScript 在 Windows 上全面運行得更快了。例如

  • Object.entries() 快了 20%
  • Array.map() 快了 50%

JIT 做了很多事情,它超過 25,000 行 C++ 程式碼!

開始使用

就是這樣 — 這就是 Bun 1.2,而這僅僅是 Bun 的開始。

我們新增了大量新功能和 API,讓建置全端 JavaScript 和 TypeScript 應用程式比以往任何時候都更加容易。

安裝 Bun

若要開始使用,請在您的終端機中執行以下任何指令。

curl
powershell
npm
brew
docker
curl
curl -fsSL https://bun.dev.org.tw/install | bash
powershell
powershell -c "irm bun.sh/install.ps1 | iex"
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,可以使用以下指令升級。

bun upgrade

我們正在徵才

我們正在招募工程師、設計師和 JavaScript 引擎 (如 V8、WebKit、Hermes 和 SpiderMonkey) 的貢獻者,以親身加入我們在舊金山的團隊,共同建構 JavaScript 的未來。

您可以查看我們的職位頁面或發送電子郵件至 email

感謝您!

Bun 是免費、開放原始碼且 MIT 授權的。

我們收到來自社群的大量開放原始碼貢獻。因此,我們要感謝所有修正錯誤或貢獻功能的人。我們感謝您的幫助!