Bun

Bun 1.1


Jarred, Chloe, Ashcon, Dylan, Meghan, Georgijs, Ciro · 2024年4月1日

Bun 是一個快速、一體化的工具集,用於從單個指令碼到全棧應用程式的 JavaScript 和 TypeScript 的執行、構建、測試和除錯。如果您是 Bun 的新手,可以在 Bun 1.0 部落格文章中瞭解更多資訊。

Bun 1.1 是一個巨大的更新。

自 Bun 1.0 以來,已提交了 1700 多次提交,我們一直在努力使 Bun 更穩定、更相容 Node.js。我們修復了上千個 bug,添加了大量新功能和 API,現在,Bun 支援 Windows 了!

Windows 支援

您現在可以在 Windows 10 及更高版本上執行 Bun 了!這對我們來說是一個巨大的里程碑,我們很高興能將 Bun 帶給全新的開發者群體。

Bun 在 Windows 上的測試透過率達到了我們 macOS 和 Linux 上 Bun 測試套件的 98%。這意味著從執行時、測試執行器、包管理器、捆綁器——所有這些在 Windows 上都能正常工作。

要在 Windows 上開始使用 Bun,請在終端中執行以下命令

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

Windows 上的 bun install

Bun 內建了一個與 npm 相容的包管理器,用於安裝軟體包。在安裝 Vite React App 時,在 Windows 上,bun install 的執行速度比 yarn 快 18 倍,比 npm 快 30 倍。

在 Windows 上使用 `--ignore-scripts` 安裝 Vite React App 中的依賴項所花費的時間。

Windows 上的 bun run

您還可以使用 bun run 來執行指令碼,這是 npm run 的更快替代方案。為了讓 bun run 在 Windows 上更快,我們設計了一種新的檔案格式:.bunx

.bunx 檔案是一個跨檔案系統的符號連結,能夠使用 Bun 或 Node.js 啟動指令碼或可執行檔案。我們決定建立這個是因為有幾個原因:

  • 符號連結在 Windows 上不一定能正常工作。
  • Windows 不會讀取檔案頂部的 shebang(#!/usr/bin/env bun)。
  • 我們希望避免為每個可執行檔案建立三種變體:.cmd.sh.ps1
  • 我們希望避免出現令人困惑的“終止批處理作業?(Y/n)”提示。

最終結果是,bun runnpm run 快 11 倍,bunx 也比 npx 快 11 倍。

在 Windows 上執行 `bunx cowsay` 與 `npx cowsay` 所花費的時間。

即使您只將 Bun 用作包管理器而不是執行時,.bunx 也能與 Node.js 完美配合。這也解決了 Windows 開發人員在向正在執行的指令碼傳送 ctrl-c 時遇到的惱人的“終止批處理作業?”提示。

Windows 上的 bun --watch

Bun 內建了對 --watch 模式的支援。這使您在進行更改和讓這些更改影響程式碼之間擁有快速的迭代週期。在 Windows 上,我們確保優化了從 control-s 到程序重新載入之間的時間。

左側是修改測試檔案,右側是在 Windows 上執行 `bun test --watch`。

Windows 上的 Node.js API

我們還花時間優化了 Node.js API,使其在 Windows 上使用最快的系統呼叫。例如,Bun 上的 fs.readdir() 比 Windows 上的 Node.js 快 58%

在 Windows 上列出目錄中的檔案,重複 1000 次所花費的時間。

雖然我們還沒有最佳化所有 API,但如果您在 Windows 上發現有任何 API 速度慢或比 Node.js 慢,請提交 issue,我們將找出使其更快的方法。

Bun 是一個 JavaScript 執行時

與自 Bun 1.0 以來新增的數十項新功能、API 和改進相比,Windows 支援只是其中一個例子。

大型專案啟動速度提高 2 倍

Bun 內建對 JavaScript、TypeScript 和 JSX 的支援,由 Bun 自己編寫的高度最佳化的原生程式碼轉譯器提供支援。

自 Bun 1.0 以來,我們為大於 50KB 的檔案實現了一個內容定址快取,以避免重複轉譯檔案帶來的效能開銷。

這使得命令列工具(如 tsc)的執行速度比 Bun 1.0 快 2 倍

在 Bun 和 Node.js 中執行 `tsc --help` 所花費的時間。

Bun Shell

Bun 現在是一個跨平臺 shell——類似於 bash,但在 Windows 上也可用。

JavaScript 是世界上最受歡迎的指令碼語言。那麼,為什麼執行 shell 指令碼如此複雜呢?

import { spawnSync } from "child_process";

// this is a lot more work than it could be
const { status, stdout, stderr } = spawnSync("ls", ["-l", "*.js"], {
  encoding: "utf8",
});

不同的平臺也有不同的 shell,它們具有略微不同的語法規則、行為,甚至命令。例如,如果您想在 Windows 上使用 cmd 執行 shell 指令碼

  • rm -rf 不起作用。
  • FOO=bar <command> 不起作用。
  • which 不存在。(它被稱為 where

Bun Shell 是一個詞法分析器、解析器和直譯器,它實現了類似 bash 的程式語言,以及 lsrmcat 等核心實用程式。

該 shell 還可以使用 Bun.$ API 從 JavaScript 和 TypeScript 中執行。

import { $ } from "bun";

// pipe to stdout:
await $`ls *.js`;

// pipe to string:
const text = await $`ls *.js`.text();

該語法使得在 shell 和 JavaScript 之間傳遞引數、緩衝區和管道變得容易。

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

// pipe a response as stdin,
// pipe the stdout back to JavaScript:
const stdout = await $`gzip -c < ${response}`.arrayBuffer();

變數也會被轉義,以防止命令注入。

const filename = "foo.js; rm -rf /";

// ls: cannot access 'foo.js; rm -rf /':
// No such file or directory
await $`ls ${filename}`;

您可以透過執行 bun run 來使用 Bun Shell 執行 shell 指令碼。

bun run my-script.sh

在 Windows 上使用 bun run 執行 package.json 指令碼時,Bun Shell 預設啟用。要了解更多資訊,請檢視文件或公告部落格文章

Bun.Glob

Bun 現在內建了用於使用 glob 模式匹配檔案和字串的 Glob API。它類似於流行的 Node.js 庫,如 fast-globmicromatch,但它匹配字串的速度快 3 倍

使用 glob.match() 將字串與 glob 模式進行匹配。

import { Glob } from "bun";

const glob = new Glob("**/*.ts");
const match = glob.match("src/index.ts"); // true

使用 glob.scan() 來列出匹配 glob 模式的檔案,使用 AsyncIterator

const glob = new Glob("**/*.ts");

for await (const path of glob.scan("src")) {
  console.log(path); // "src/index.ts", "src/utils.ts", ...
}

Bun.Semver

Bun 有一個新的 Semver API,用於解析和排序 semver 字串。它類似於流行的 node-semver 包,但速度快 20 倍

使用 semver.satisfies() 檢查版本是否滿足範圍。

import { semver } from "bun";

semver.satisfies("1.0.0", "^1.0.0"); // true
semver.satisfies("1.0.0", "^2.0.0"); // false

使用 semver.order() 比較兩個版本或對版本陣列進行排序。

const versions = ["1.1.0", "0.0.1", "1.0.0"];
versions.sort(semver.order); // ["0.0.1", "1.0.0", "1.1.0"]

Bun.stringWidth()

Bun 還支援新的 string-width API,用於測量字串在終端中的可見寬度。當您想知道字串在終端中會佔用多少列時,這將非常有用。

它類似於流行的 string-width 包,但速度快 6000 倍

import { stringWidth } from "bun";

stringWidth("hello"); // 5
stringWidth("👋"); // 2
stringWidth("你好"); // 4
stringWidth("👩‍👩‍👧‍👦"); // 2
stringWidth("\u001b[31mhello\u001b[39m"); // 5

它支援 ANSI 轉義碼、全形字元、字形和表情符號。它還支援 Latin1、UTF-16 和 UTF-8 編碼,併為每種編碼進行了最佳化實現。

server.url

當您使用 Bun.serve() 建立 HTTP 伺服器時,現在可以透過 server.url 屬性獲取伺服器的 URL。這在測試中獲取格式化的伺服器 URL 時非常有用。

import { serve } from "bun";

const server = serve({
  port: 0, // random port
  fetch(request) {
    return new Response();
  },
});

console.log(`${server.url}`); // "https://:1234/"

server.requestIP()

您還可以使用 server.requestIP() 方法獲取 HTTP 請求的 IP 地址。這不會讀取 X-Forwarded-ForX-Real-IP 等標頭。它只是返回套接字的 IP 地址,該地址可能對應於代理的 IP 地址。

import { serve } from "bun";

const server = serve({
  port: 0,
  fetch(request) {
    console.log(server.requestIP(request)); // "127.0.0.1"
    return new Response();
  },
});

subprocess.resourceUsage()

當您使用 Bun.spawn() 啟動子程序時,現在可以透過 resourceUsage() 方法訪問程序的 CPU 和記憶體使用情況。這對於監控程序效能非常有用。

import { spawnSync } from "bun";

const { resourceUsage } = spawnSync([
  "bun",
  "-e",
  "console.log('Hello world!')",
]);

console.log(resourceUsage);
// {
//   cpuTime: { user: 5578n, system: 4488n, total: 10066n },
//   maxRSS: 22020096,
//   ...
// }

import.meta.env

Bun 現在支援使用 import.meta.env 設定環境變數。它是 process.envBun.env 的別名,併為了與其他 JavaScript 生態系統中的工具(如 Vite)相容而存在。

import.meta.env.NODE_ENV; // "development"

Node.js 相容性

Bun 旨在成為 Node.js 的直接替代品。

Node.js 相容性仍然是 Bun 的首要任務。我們對 Bun 對 Node.js API 的支援進行了許多改進和修復。以下是一些亮點:

HTTP/2 客戶端

Bun 現在支援 node:http2 客戶端 API,允許您發出出站 HTTP/2 請求。這也意味著您可以使用 @grpc/grpc-js 等包透過 HTTP/2 傳送 gRPC 請求。

import { connect } from "node:http2";

const client = connect("https://example.com/");
const request = client.request({ ":path": "/" });

request.on("response", (headers, flags) => {
  for (const name in headers) {
    console.log(`${name}: ${headers[name]}`);
    // "cache-control: max-age=604800", ...
  }
});

request.on("end", () => {
  client.close();
});

request.end();

我們仍在努力新增對 HTTP/2 伺服器的支援,您可以在此 issue 中跟蹤我們的進度。

Date.parse() 與 Node.js 相容

Bun 使用 JavaScriptCore 作為其 JavaScript 引擎,而 Node.js 使用 V8Date 解析很複雜,並且在不同引擎之間的行為差異很大。

例如,在 Bun 1.0 中,以下 Date 在 Node.js 中可以工作,但在 Bun 中不行:

const date = "2020-09-21 15:19:06 +00:00";

Date.parse(date); // Bun: Invalid Date
Date.parse(date); // Node.js: 1600701546000

為了解決這些不一致之處,我們將 V8 的 Date 解析器移植到了 Bun。這意味著 Date.parsenew Date() 在 Bun 中的行為與在 Node.js 中相同。

遞迴 fs.readdir()

在 Bun 1.0 中,我們不支援 fs.readdir()recursive 選項。這是一個疏忽,並導致了許多軟體包出現細微的 bug。

我們不僅添加了對 recursive 選項的支援,而且使其比 Node.js 快 22 倍。

在大型目錄中使用遞迴 `fs.readdir()` 列出檔案所花費的時間。

Bun 和 Node.js 之間的 IPC 支援

您現在可以使用 ipc 選項在 Bun 和 Node.js 程序之間傳送 IPC 訊息。這也修復了一個在 Next.js 14.1 時會導致 Bun 掛起的 bug。

if (typeof Bun !== "undefined") {
  const prefix = `[bun ${process.versions.bun} 🐇]`;
  const node = Bun.spawn({
    cmd: ["node", __filename],
    ipc({ message }) {
      console.log(message);
      node.send({ message: `${prefix} 👋 hey node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 hey node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 hey bun` });
  });
}

未文件化的 Node.js API

Node.js 有大量的未文件化 API,您無法透過閱讀其文件找到它們。

有數百萬個 npm 包,不可避免地有些包會依賴於晦澀或未文件化的 API。我們沒有讓這些包破損或被遺忘,而是將這些 API 新增到 Bun 中,這樣您就不需要重寫程式碼了。

例如,ServerResponse 有一個未文件化的 _headers 屬性,允許將 HTTP 標頭作為物件進行修改。

import { createServer } from "node:http";

createServer((req, res) => {
  const { _headers } = res;
  delete _headers["content-type"];
  res._implicitHeader();
  res.end();
});

這個 API 在最近的 Astro 版本中被使用,然後我們在 Bun 中進行了修復。還有一個 _implicitHeader() 函式,它被 Express 使用,我們也對此進行了修復。

還有更多

我們還新增或修復了許多其他 Node.js API,包括:

模組API
頂層添加了 import.meta.filenameimport.meta.dirnamemodule.parent
process添加了 getReport()binding("tty_wrap")
node:util添加了 domainToASCII()domainToUnicode()styleText()。更改了 inspect() 以使其更符合 Node.js 的行為
node:crypto添加了 KeyObjectcreatePublicKey()createPrivateKey()generateKeyPair()generateKey()sign()verify()
node:fs添加了 openAsBlob()opendir()fdatasync()。修復了 FileHandle 未隨各種 API 返回的問題
node:console添加了 Console
node:dns添加了 lookupService()
node:http透過 request() 支援 Unix domain sockets
node:events添加了 on()
node:path修復了 Windows 路徑的許多 bug。
node:vm添加了 createScript()
node:os添加了 availableParallelism()

Web API

Bun 還支援 Web 標準 API,包括 fetch()Response。這使得編寫能在瀏覽器和 Bun 中工作的程式碼更加容易。

自 Bun 1.0 以來,我們在 Web API 方面進行了許多改進和修復。

WebSocket 穩定可用

以前,WebSocket 被標記為實驗性,因為它存在協議 bug,例如過早斷開連線和分片問題。

在 Bun 1.1 中,WebSocket 現在是穩定的,並通過了行業標準的 Autobahn 一致性測試套件。這修復了 WebSocket 客戶端中的數十個 bug,使其在生產環境中使用更可靠。

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

ws.addEventListener("message", ({ data }) => {
  console.log("Received:", data);
});

ws.addEventListener("open", () => {
  ws.send("Hello!");
});

performance.mark()

Bun 現在支援 user-timings API,其中包括 performance.mark()performance.measure() 等 API。這對於測量應用程式的效能非常有用。

performance.mark("start");
while (true) {
  // ...
}
performance.mark("end");
performance.measure("task", "start", "end");

使用 Brotli 壓縮的 fetch()

您現在可以使用 fetch() 發起帶有 br 編碼的請求。這對於向支援 Brotli 壓縮的伺服器傳送請求非常有用。

const response = await fetch("https://example.com/", {
  headers: {
    "Accept-Encoding": "br",
  },
});

URL.canParse()

Bun 現在支援最近新增的 URL.canParse() API。這使得可以在不丟擲錯誤的情況下檢查一個字串是否為有效的 URL。

URL.canParse("https://example.com:8080/"); // true
URL.canParse("apoksd!"); // false

透過 Unix 套接字進行 fetch()

Bun 現在支援透過 Unix 套接字傳送 fetch() 請求。

const response = await fetch("https:///info", {
  unix: "/var/run/docker.sock",
});

const { ID } = await response.json();
console.log("Docker ID:", ID); // <uuid>

雖然這不是瀏覽器會支援的 API,但對於需要透過 Unix 套接字與服務(如 Docker 守護程序)通訊的伺服器端應用程式非常有用。

Response 主體作為 AsyncIterator

您現在可以將 AsyncIterator 傳遞給 Response 建構函式。這對於從不支援 ReadableStream 的源流式傳輸資料很有用。

const response = new Response({
  async *[Symbol.asyncIterator]() {
    yield "Hello, ";
    yield Buffer.from("world!");
  },
});
await response.text(); // "Hello, world!"

這是 fetch() API 的一個非標準擴充套件,Node.js 支援該擴充套件,並已新增到 Bun 中以確保相容性。

其他更改

Bun 是一個 npm 相容的包管理器

即使您不將 Bun 用作執行時,您仍然可以將 bun install 用作包管理器。Bun 是一個 npm 相容的包管理器,其安裝包的速度比 npm 快 29 倍。

自 Bun 1.0 釋出以來,我們顯著提高了 bun install 的穩定性和效能。我們修復了數百個錯誤,添加了新功能,並改進了整體開發人員體驗。

生命週期指令碼

如果您在 Bun 1.0 中使用 bun install 時遇到過 bug,那很可能與生命週期指令碼有關。生命週期指令碼是在包安裝過程中執行的指令碼,例如 postinstall

在 Bun 1.1 中,我們修復了許多此類 bug,並徹底重寫了生命週期指令碼的工作方式。

Windows 上的生命週期指令碼

在 Windows 上,生命週期指令碼使用 Bun Shell。這意味著您不需要像

  • rimraf
  • cross-env
  • node-which

trustedDependencies

預設情況下,Bun 不會為不受信任的包執行生命週期指令碼。這是一項安全功能,用於防止惡意指令碼在您的計算機上執行。Bun 只會執行您 package.json 檔案中 trustedDependencies 列表中定義的指令碼。

當您首次新增包時,Bun 會通知您該包是否有未執行的生命週期指令碼。

bun add v1.1.0
Saved lockfile

installed @biomejs/biome@1.6.1 with binaries:
- biome

1 package installed [55.00ms]

Blocked 1 postinstall. Run `bun pm untrusted` for details.

bun pm untrusted

如果您想檢視哪些指令碼被阻止了,可以執行 bun pm untrusted

bun pm untrusted v1.1.0

./node_modules/@biomejs/biome @1.6.1
» [postinstall]: node scripts/postinstall.js

These dependencies had their lifecycle scripts blocked during install.

If you trust them and wish to run their scripts, use `bun pm trust`.

bun pm trust

如果您信任該包,可以執行 bun pm trust [package]。如果您想信任所有包,也可以執行 bun pm trust --all

bun pm trust v1.1.0

./node_modules/@biomejs/biome @1.6.1
[postinstall]: node scripts/postinstall.js

1 script ran across 1 package [71.00ms]

bun add --trust

如果您已經知道想要信任某個依賴項,可以使用 bun add --trust [package] 來新增它。這將把該包及其傳遞依賴項新增到您的 trustedDependencies 列表中,因此您無需為該包執行 bun pm trust

{
  "dependencies": {
    "@biomejs/biome": "1.6.1"
  },
   "trustedDependencies": [
     "@biomejs/biome"
   ]
}

Bun 包含一個預設的白名單,其中包含流行的包,這些包包含已知的安全生命週期指令碼。您可以透過執行 bun pm default-trusted 來檢視完整列表。

此外,為了減少生命週期指令碼對效能的影響,我們使其並行執行。這意味著生命週期指令碼將同時執行,從而減少了安裝包所需的時間。

bun pm migrate

Bun 使用二進位制鎖檔案 bun.lockb 來加快 bun install 的安裝速度。

您現在可以執行 bun pm migrate 來將 package-lock.json 檔案轉換為 bun.lockb 檔案。如果您想從 npm 一次性遷移到 Bun,這很有用。

bun pm migrate
[5.67ms] migrated lockfile from package-lock.json

 21 packages installed [54.00ms]

如果您使用 bun install,則無需執行此命令,因為它會自動遷移鎖檔案(如果檢測到 package-lock.json 檔案)。

Bun 是一個 JavaScript 打包器

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

bun build --target=node

Bun 可以使用 --target=node 標誌將程式碼打包為在 Node.js 上執行。

var { promises } = require("node:fs");
var { join } = require("node:path");

promises.readFile(join(__dirname, "data.txt"));

在 Bun 1.0 中,有幾個 bug 導致此功能無法正常工作,例如無法 require 內建模組,如 node:fsnode:path。在 Bun 1.0 中,它看起來是這樣的:

bun-1.0 build --target=node app.ts --outfile=dist.mjs
node dist.mjs
TypeError: (intermediate value).require is not a function
    at __require (file:///app.mjs:2:22)
    at file:///app.mjs:7:20
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:28:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)

在 Bun 1.1 中,這些 bug 已得到修復。

bun build --target=node app.ts --outfile=dist.mjs
node dist.mjs

bun build --compile

Bun 可以使用 --compile 標誌將 TypeScript 和 JavaScript 檔案編譯為單個可執行檔案。

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

在 Bun 1.1 中,您還可以嵌入 NAPI (n-api) 外掛 .node 檔案。這對於打包原生 Node.js 模組(如 @anpi-rs/canvas)非常有用。

import { promises } from "fs";
import { createCanvas } from "@napi-rs/canvas";

const canvas = createCanvas(300, 320);
const data = await canvas.encode("png");

await promises.writeFile("empty.png", data);

然後,您可以將應用程式編譯並執行為單個可執行檔案。

bun build --compile canvas.ts
./canvas # => simple.png

Bun 擁有強大的宏系統,允許您在編譯時轉換程式碼。宏可用於生成程式碼、最佳化程式碼,甚至在編譯時執行程式碼。

在 Bun 1.1 中,您現在可以在打包時匯入內建模組。

在打包時將檔案讀取為字串

import { readFileSync } from "node:fs"
  with { type: "macro" };

export const contents = readFileSync("hello.txt", "utf8");

在打包時生成程序

import { spawnSync } from "node:child_process"
  with { type: "macro" };

const result = spawnSync("echo", ["Hello, world!"], {encoding: "utf-8"}).stdout;
console.log(result); // "Hello, world!"

Bun 是一個測試執行器

Bun 內建了測試模組,可以輕鬆地在 JavaScript、TypeScript 和 JSX 中編寫和執行測試。它支援與 Jest 相同的 API,其中包括 expect() 風格的 API。

匹配器

匹配器是可用於測試程式碼的斷言。自 Bun 1.0 起,我們添加了數十個新的 expect() 匹配器,包括:

import { expect } from "bun:test";

expect.hasAssertions();
expect.assertions(9);
expect({}).toBeObject();
expect([{ foo: "bar" }]).toContainEqual({ foo: "bar" });
expect(" foo ").toEqualIgnoringWhitespace("foo");
expect("foo").toBeOneOf(["foo", "bar"]);
expect({ foo: Math.PI }).toEqual({ foo: expect.closeTo(3.14) });
expect({ a: { b: 1 } }).toEqual({ a: expect.objectContaining({ b: 1 }) });
expect({ a: Promise.resolve("bar") }).toEqual({ a: expect.resolvesTo("bar") });
expect({ b: Promise.reject("bar") }).toEqual({ b: expect.rejectsTo("bar") });
expect.unreachable();

使用 expect.extend() 自定義匹配器

如果 Bun 不支援某個匹配器,您可以使用 expect.extend() 建立自己的匹配器。當您想定義一個可在多個測試中重複使用的自定義匹配器時,這很有用。

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

expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    if (pass) {
      return {
        message: () =>
          `Expected ${received} not to be within range ${floor} - ${ceiling}`,
        pass: true,
      };
    } else {
      return {
        message: () =>
          `Expected ${received} to be within range ${floor} - ${ceiling}`,
        pass: false,
      };
    }
  },
});

test("toBeWithinRange()", () => {
  expect(1).toBeWithinRange(1, 99); // ✅
  expect(100).toBeWithinRange(1, 99); // ❌ Expected 100 to be within range 1 - 99
});

模組模擬

Bun 現在支援模組模擬。

  • 與 Jest 不同,Bun 可以模擬 ESM 和 CommonJS 模組。
  • 如果模組已被匯入,Bun 可以就地更新模組,這意味著模擬在執行時有效。其他測試執行器無法做到這一點,因為它們在構建時設定模擬。
  • 您可以覆蓋任何內容:本地檔案、npm 包和內建模組。
file.js
package.js
built-in.js
file.js
import { mock, test, expect } from "bun:test";
import { fn } from "./mock";

test("mocking a local file", async () => {
  mock.module("./mock", () => {
    return {
      fn: () => 42,
    };
  });

  // fn is already imported, so it will be updated in-place
  expect(fn()).toBe(42);

  // also works with cjs
  expect(require("./mock").fn()).toBe(42);
});
package.js
import { mock, test, expect } from "bun:test";
import stringWidth from "string-width";

test("mocking an npm package", async () => {
  mock.module("string-width", () => {
    return {
      default: Bun.stringWidth,
    };
  });

  const string = "hello";
  expect(stringWidth()).toBe(5);
  expect(require("string-width")()).toBe(5);
});
built-in.js
import { mock, test, expect } from "bun:test";
import { readFileSync } from "node:fs";

test("mocking a built-in module", async () => {
  mock.module("node:fs", () => {
    return {
      readFileSync: () => "mocked!",
    };
  });

  expect(readFileSync("./foo.txt")).toBe("mocked!");
  expect(require("fs").readFileSync("./bar.txt")).toBe("mocked!");
});

Bun 內建支援 SQLite

自 1.0 版本以來,Bun 就內建了對 SQLite 的支援。它擁有一個受 better-sqlite3 啟發的 API,但它是用原生程式碼編寫的,速度更快。

import { Database } from "bun:sqlite";

const db = new Database(":memory:");
const query = db.query("select 'Bun' as runtime;");
query.get(); // { runtime: "Bun" }

從那時起,bun:sqlite 進行了許多新功能和改進。

多語句查詢

我們添加了對多語句查詢的支援,這允許在一次呼叫 run()exec() 時執行多個 SQL 語句,語句之間用 ; 分隔。

import { Database } from "bun:sqlite";

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

db.run(`
  CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT
  );

  INSERT INTO users (name) VALUES ("Alice");
  INSERT INTO users (name) VALUES ("Bob");
`);

詳細錯誤

bun:sqlite 丟擲錯誤時,您現在會看到更詳細的錯誤資訊,包括表和列名。在 Bun 1.0 中,錯誤訊息很簡短,不包含額外詳細資訊。

- error: constraint failed
+ SQLiteError: UNIQUE constraint failed: foo.bar
+   errno: 2067
+   code: "SQLITE_CONSTRAINT_UNIQUE"
      at run (bun:sqlite:185:11)
      at /index.js:7:1

匯入資料庫

在 Bun 1.1 中,您現在可以使用 import 語法匯入 SQLite 資料庫。這使用了新的 匯入屬性功能來指定匯入型別。

import db from "./users.db"
  with { type: "sqlite" };

const { n } = db
  .query("SELECT COUNT(id) AS n FROM users")
  .get();

console.log(`Found ${n} users!`); // "Found 42 users!"

嵌入資料庫

您還可以將應用程式和 SQLite 資料庫編譯成一個 單個可執行檔案。要啟用此功能,請在匯入時指定 embed 屬性,然後使用 bun build --compile 來構建您的應用程式。

import db from "./users.db"
  with { type: "sqlite", embed: "true" };
bun build --compile ./app.ts
./app
Found 42 users!

Bun 使 JavaScript 更簡單

我們花費了大量時間來思考 Bun 的開發人員體驗。我們希望讓編寫、執行和除錯 JavaScript 和 TypeScript 程式碼變得容易。

命令、輸出和錯誤訊息得到了大量改進,使 Bun 更易於使用。

語法高亮錯誤

當 Bun 中丟擲錯誤時,它會將堆疊跟蹤列印到控制檯,並帶有多行原始碼預覽。現在,該原始碼預覽會進行語法高亮,使其更易於閱讀。

錯誤(帶和不帶語法高亮)的預覽。

簡化堆疊跟蹤

來自 Error.stack 的堆疊跟蹤現在包含的噪音更少,例如與錯誤無關的內部函式。這使得更容易看到錯誤發生的位置。

1 | throw new Error("Oops");
          ^
error: Oops
    at /oops.js:1:7
    at globalThis (/oops.js:3:14)
    at overridableRequire (:1:20)
    at /index.js:3:8
    at globalThis (/index.js:3:8)

bun --eval

您可以執行 bun --eval,或簡寫為 bun -e,來執行指令碼而不建立檔案。就像 Bun 的其他功能一樣,它支援頂層 await、ESM、CommonJS、TypeScript 和 JSX。

您可以將指令碼作為字串傳遞。

bun -e 'console.log(Bun.version)'
1.1.0

或者,您可以使用 bun - 透過 stdin 管道傳輸指令碼。

echo 'console.log(await fetch("https://example.com/"))' | bun -
Response (1.26 KB) {
  status: 200, ...
}

bun --print

您也可以使用 bun --print,它與 bun -e 相同,只是它會使用 console.log() 列印最後一個語句。

bun --print 'await Bun.file("package.json").json()'
{
  name: "bun",
  dependencies: { ... },
}

您也可以省略 await,因為 Bun 會檢測懸空 Promise。

bun --print 'fetch("https://example.com/").then(r => r.text())'
<!DOCTYPE html>
...

bun --env-file

Bun 預設檢測並載入 .env 檔案,但現在您可以使用 bun --env-file 來載入自定義 .env 檔案。這對於測試不同環境很有用。

bun --env-file=custom.env src/index.ts
bun --env-file=.env.a --env-file=.env.b run build

您可以在執行 JavaScript 檔案時或執行 package.json 指令碼時使用 --env-file

行為更改

Bun 1.1 包含一些小的行為調整,您應該瞭解這些調整,但我們認為它們極不可能破壞您的程式碼。

更長的網路超時

在 Bun 1.0 中,fetch()bun install 的預設網路超時時間為 30 秒。

Bun 1.0.4 起,預設網路超時時間已增加到 5 分鐘。這與 Google Chrome 的預設設定一致,並且應該有助於處理高延遲連線。

您也可以使用以下方式停用 fetch() 的超時:

const response = await fetch("https://example.com/", {
  timeout: false,
});

Bun.write() 會建立父目錄

以前,如果父目錄不存在,Bun.write() 會丟擲錯誤。

import { write } from "bun";

await write("does/not/exist/hello.txt", "Hello!");
// ENOENT: No such file or directory

Bun 1.0.16 起,如果父目錄不存在,Bun 會建立它。

雖然這與 fs.writeFileSync() 等 API 的行為不符,但開發者要求我們做出此更改,以便 API 更直觀易用,並帶來更好的開發人員體驗。

如果沒有此更改,開發者將不得不編寫以下樣板程式碼:

import { write } from "bun";
import { mkdir } from "node:fs/promises";

try {
  await write("does/not/exist/hello.txt", "Hello!");
} catch (error) {
  if (error.code === "ENOENT") {
    await mkdir("does/not/exist", { recursive: true });
    await write("does/not/exist/hello.txt", "Hello!");
  } else {
    throw error;
  }
}

如果您想恢復到舊的行為,可以指定 createPath 屬性。

import { write } from "bun";

await write("does/not/exist/hello.txt", "Hello, world!", { createPath: false });
// ENOENT: No such file or directory

條件匯出不包含 worker

包可以使用 條件匯出 來為不同環境指定不同的入口檔案。例如,包可能為瀏覽器定義一個 browser 匯出,為 Node.js 定義一個 node 匯出。

{
  "exports": {
    "node": "./node.js",
    "browser": "./browser.js",
    "worker": "./worker.js"
  }
}

在 Bun 1.0 中,Bun 會按以下順序選擇第一個匯出:bunworkernode

在 Bun 1.1 中,Bun 將不再選擇 worker 匯出,因為這與 Web Workers 相關,而 Web Workers 通常假定是類瀏覽器環境。

此更改僅在 Bun 用作執行時起作用,並修復了各種 bug,這些 bug 導致 worker 匯出在比更合適的 node 匯出之前被選中。

預設情況下 NODE_ENVundefined

在 Node.js 中,process.env.NODE_ENV 預設設定為 undefined

在 Bun 開發的早期,我們將預設值設定為 development,結果證明這是一個錯誤。這是因為開發者經常忘記將 NODE_ENV 設定為 production,這可能導致開發功能被包含在 production 構建中。

在 Bun 1.1 中,我們將預設 NODE_ENV 改為 undefined,以匹配 Node.js。

bun --print 'process.env.NODE_ENV'
undefined
NODE_ENV=development bun --print 'process.env.NODE_ENV'
development

bun install [package]@latest

以前,如果您使用 latest 標籤安裝包,它會將字面字串 latest 寫入您的 package.json。這並非預期行為,也不符合其他包管理器的行為。

在 Bun 1.1 中,latest 標籤在寫入 package.json 之前會被解析。

bun install lodash@latest
package.json
{
  "dependencies": {
     "lodash": "latest"
     "lodash": "^4.17.21"
  }
}

Bun.$ 會因非零退出碼而拒絕

Bun Shell 在 Bun 1.0.24 中引入。當子程序退出時,即使退出碼非零,Promise 也會解析。

import { $ } from "bun";

await $`cd /does/not/exist`;
// does not throw

這通常不是期望的行為,並且會導致命令失敗但 Promise 解析的 bug 被忽視。

在 Bun 1.1 中,當子程序以非零退出碼退出時,Bun Shell 現在會拒絕並丟擲錯誤。

import { $ } from "bun";

await $`cd /does/not/exist`;
// ShellError: cd /does/not/exist: No such file or directory

如果您想恢復到之前的行為,可以呼叫 throws() 函式。

import { $ } from "bun";

const { exitCode, stderr } = await $`cd /does/not/exist`.throws(false);
console.log(exitCode); // 1
console.log(stderr); // "cd: /does/not/exist: No such file or directory"

import.meta.resolve()

在 Bun 1.0 中,import.meta.resolve() 會非同步解析為絕對檔案路徑。

這與 Node.js 的原始實現行為一致。然而,出於 Web API 相容性原因,Node.js 已將該 API 改為同步。因此,Bun 也做了同樣的更改。

import.meta.resolve("./foo.js"); // Before: Promise { "/path/to/foo.js" }
import.meta.resolve("./foo.js"); // After: "file:///path/to/foo.js"

千餘個 bug 修復

自 Bun 1.0 起,我們修復了超過一千個 bug。

如果您在使用 Bun 1.0 時遇到過錯誤,我們鼓勵您嘗試使用 Bun 1.1。如果仍然存在未修復的問題,請隨時建立一個新 issue或點贊一個現有 issue。

您可以使用以下命令升級到 Bun 1.1:

bun upgrade

值得注意的修復

在修復的數千個 bug 中,以下是一些您可能遇到的、現已修復的最常見問題:

  • 執行 bun install 後出現 Module not found
  • WebSocket 錯誤或過早斷開連線。
  • Bun.file 有時會導致 EBADF: Bad file descriptor 錯誤。
  • 如果存在某些預/後標籤,bun install 會解析不正確的版本。
  • bun install --yarn 有時會生成無效的 YAML。
  • 在 Docker 容器中出現 Failed to start server. Is port in use?
  • 在 Vercel 和 Google Cloud 上出現 "pidfd_open(2)" system call is not supported
  • Bun.serve() 不響應帶有 _ 標頭的 HTTP 請求。
  • 監聽 0.0.0.0 也會繫結到 IPv6。
  • process.nextTick()setImmediate() 的執行順序與 Node.js 不同。

效能改進

我們不斷對 Bun 進行更改,使其更快、更高效。關注 @bunjavascript 以獲取“下一版本 Bun”的最新摘要。

以下是自 Bun 1.0 以來進行的一些效能改進的摘要:

入門

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

我們讓 Bun 更快、更可靠,修復了千餘個 bug,添加了大量新功能和 API,現在 Bun 支援 Windows。要開始使用,請在終端中執行以下任何命令:

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

我們正在招聘

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

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

感謝 364 位貢獻者!

Bun 是免費、開源且採用 MIT 許可。

因此,這也意味著我們收到了來自社群的各種開源貢獻。我們要感謝所有報告 issue、修復 bug 甚至貢獻功能的人。