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 測試套件。此後,我們修復了數千個 bug,以下 Node.js 模組現在在 Bun 中通過了 90% 以上的測試。

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

我們是如何做到的。

如何衡量相容性?

在 Bun 1.2 中,我們改變了測試和改進 Bun 與 Node.js 相容性的方式。以前,我們優先處理和修復報告的 Node.js bug,通常來自 GitHub issues,有人嘗試使用 Bun 中不起作用的 npm 包。

雖然這修復了真實使用者遇到的實際 bug,但這是一種“打地鼠”式的方法。它阻礙了我們進行大規模重構,而大規模重構是我們實現 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 伺服器也是必需的,Bun 現在也支援 gRPC 伺服器。以前,只有客戶端支援。

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 套接字。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 spawn 多個 Bun 例項。這通常用於透過在多個 CPU 核心上執行任務來提高吞吐量。

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

  • 主工作程序 spawn 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 重寫為原生程式碼。這不僅修復了許多 bug,而且比 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++ 外掛

如果您想在 JavaScript 程式碼旁邊使用 C++ 外掛,最簡單的方法是使用 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 中“正常工作”。這個解釋太複雜和技術性了,我們寫了一篇三部分的部落格系列,介紹我們如何在不使用 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 倍

雖然相容性對於修復 bug 很重要,但它也有助於我們修復 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 的內建支援。您可以使用與 Blob 等 Web 標準相容的 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 檔案下載到您的伺服器再發送給使用者,而是將使用者重定向到 S3 檔案的預簽名 URL。

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 擴充套件了 Bun 對 SQL 資料庫的支援,這是一個內建的 SQL 客戶端,支援 Postgres。我們還有一個拉取請求,即將新增對 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 是用原生程式碼編寫的,並進行了最佳化,例如:

  • 自動預處理語句
  • 查詢流水線
  • 二進位制線協議支援
  • 連線池
  • 結構快取

類似《魔獸世界》中 Buff 的最佳化堆疊。

結果是,與使用 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 作為執行時。

bun install 替換 npm install

$ npm install
$ bun install

在 Bun 1.2 中,我們對包管理器進行了最大的更改。

bun.lockb 的問題

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

與其他使用 JSON 或 YAML 等文字鎖定檔案的包管理器不同,二進位制鎖定檔案使 bun install 的速度比 npm 快近 30 倍。

但是,我們發現使用二進位制鎖定檔案有很多“紙面上的問題”。首先,您無法在 GitHub 和其他平臺上檢視鎖定檔案的內容。這很糟糕。

如果您收到外部貢獻者更改 bun.lockb 檔案的拉取請求,會發生什麼?您會信任它嗎?可能不會。

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

這也使得工具難以讀取鎖定檔案。例如,像 Dependabot 這樣的依賴管理工具需要一個 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": { /* ... */ },
}

這使得在拉取請求中檢視 diffs 更加容易,並且尾隨逗號可以大大降低合併衝突的可能性。

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

對於現有的帶有 bun.lockb 檔案的專案,Bun 將繼續支援二進位制鎖定檔案,*而無需遷移到新的鎖定檔案*。我們將繼續*長期*支援二進位制鎖定檔案,因此您可以繼續使用 bun addbun update 等命令,它將更新您的 bun.lockb 檔案。

bun install 速度提升 30%

您可能認為,在遷移到文字鎖定檔案後,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 依賴項列表,以及哪些版本已過時。“更新”列顯示下一個 semver 匹配的版本,而“最新”列顯示最新版本。

如果您注意到有特定依賴項想要更新,您可以使用 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

有時,您的依賴項存在 bug 或缺少功能。雖然您可以 fork 該包,進行修改,然後釋出它——但這需要大量工作。如果您不想維護 fork,怎麼辦?

在 Bun 1.2 中,我們添加了對修補依賴項的支援。工作原理如下:

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

Bun 會在 patches/ 目錄中生成一個包含您更改的 .patch 檔案,該檔案會在 bun install 時自動應用。然後,您可以將此補丁檔案提交到您的儲存庫,並與您的團隊共享。

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

./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 標誌,該標誌允許您省略開發、可選或對等依賴項。

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 匯入的支援。這使您可以使用單個匯入語句替換整個前端工具鏈。

要開始使用,請將 HTML 匯入傳遞給 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 檔案的位元組碼快取。兩者都需要執行,因為位元組碼編譯目前不會編譯非同步函式、生成器或 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 完全模糊時,我們可以將其用作最後的啟發式。

外掛 API

Bun 有一個通用的 外掛 API,用於擴充套件打包器*和*執行時。

您可以使用外掛來攔截 import() 語句,為 .yaml 等副檔名新增自定義載入器,併為 Bun 實現框架。

onBeforeParse()

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

與執行 JavaScript 程式碼的現有生命週期鉤子不同,此鉤子必須是 N-API 外掛,它可以用 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 中的橫幅和頁尾選項在 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 工具支援特殊註解,這些註解可以影響死程式碼消除期間的行為。例如,@__PURE__ 註解告訴打包器函式呼叫是純淨的(無論它是否確實如此),並且如果未使用,則可以刪除該呼叫。

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

有時,庫可能包含錯誤的註解,這可能導致 Bun 刪除本應存在的副作用。

為了解決這些問題,您可以在執行 bun build 時使用 --ignore-dce-annotations 標誌來忽略所有註解。這僅在死程式碼消除破壞 bundle 時才應使用,並且修復註解應優先於保留此標誌。

--packages=external

您現在可以控制是否將包依賴項包含在 bundle 中。如果匯入不以 .../ 開頭,則認為它是包。

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

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

內建 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 將被展平到一個 CSS 檔案中,而不是向 bundle 新增額外的 .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 伺服器,可以使用 RequestResponse 等標準 API 輕鬆響應 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 套接字支援。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 套接字 API 專為高效能而設計。與 Node.js 不同,它可以使用單個系統呼叫傳送多個 UDP 資料報,並支援響應作業系統返回的背壓。

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"],
]);

這對於構建需要將遊戲狀態更新廣播給每個對等方的遊戲伺服器非常有用。

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) 將查詢結果對映到類的例項。這允許您附加方法、getter 和 setter,而無需使用 ORM。

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 的實驗性支援。這是一種在沒有構建步驟的情況下使用 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 編譯器。驚喜!

gccclang 等傳統 C 編譯器(編譯一個簡單程式可能需要幾秒鐘)不同,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!"

node-gyp 需要構建步驟不同,只要您安裝了 Bun,它就能正常工作。

musl 支援

在 Bun 1.2 中,我們推出了一種新的 Bun 構建版本,可在那些使用 musl libc 而不是 glibc 的 Linux 發行版(如 Alpine Linux)上執行。這同時支援 Linux x64 和 aarch64。

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

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

雖然 musl 允許更小的容器映象,但它的效能通常比 glibc 版本慢一些。我們建議使用 glibc,除非您有特定理由使用 musl。

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,該 Promise 在您呼叫 resolvereject 函式時解析或拒絕。

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:vminstanceof 可能會返回假陰性。

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 與十六進位制字串相互轉換。

  • 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);

bytes() 方法支援多個 API,包括 ResponseBlobBun.file()

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 倍

request.json() 和類似方法的快速路徑現在可以在訪問請求 body 後工作。這使得某些 Bun.serve() 應用程式的吞吐量提高高達 2 倍。

Error.captureStackTrace() 速度提高 9 倍
fs.readFile() 速度提高 10%

對於小檔案,fs.readFile() 的速度提高高達 10%。

console.log(String) 速度提高 50%

當您使用字串作為引數的 console.log() 時,速度現在提高了 50%。

Windows 上的 JavaScript 更快

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

JIT,或即時編譯,是一種在執行時而不是提前編譯程式碼的技術。這使 JavaScript 執行得更快,但實現起來也更加複雜。

總體而言,Windows 上的 JavaScript 現在執行得更快。例如:

  • 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.nodejs.com.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

我們正在招聘

我們正在招聘工程師、設計師以及像 V8、WebKit、Hermes 和 SpiderMonkey 這樣的 JavaScript 引擎的貢獻者,加入我們在舊金山的現場團隊,共同構建 JavaScript 的未來。

您可以檢視我們的 招聘 頁面或傳送 電子郵件

謝謝!

Bun 是免費的、開源的,並採用 MIT 許可證。

我們收到了社群的大量開源貢獻。因此,我們要感謝每一位修復了 bug 或貢獻了功能的貢獻者。我們非常感謝您的幫助!