Bun

Bun v1.0.23


Jarred Sumner · 2024年1月16日

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

此版本修正了 40 個錯誤(解決了 194 個 👍 表情符號回應)。在 Bun 中導入 & 嵌入 sqlite 資料庫、資源管理('using' TC39 stage3)支援、為 Node.js 建置時的打包器改進、peer dependency 解析的錯誤修正、semver 錯誤修正、Linux 上 TCP 速度提升 4%、Node.js 相容性改進等等。

先前的版本

  • v1.0.22 修正了 29 個錯誤(解決了 118 個 👍 表情符號回應)、修正了 Vercel 上的 bun install 問題、新增了 performance.mark() API、為額外的管道新增了 child_process 支援、加快了 Buffer.concat 速度、新增了 toBeEmptyObjecttoContainKeys 匹配器、修正了使用 emoji 的 console.table 寬度,以及 worker_threads 中對 argvexecArgv 選項的支援,並支援 fetch 中的 Brotli。
  • v1.0.21 - 修正了 33 個錯誤(解決了 80 個 👍 表情符號回應)。console.table() 支援。Bun.write、Bun.file 和 bun:sqlite 使用更少的記憶體。使用 FormData 上傳大型檔案使用更少的記憶體。bun:sqlite 錯誤訊息變得更詳細。修正了 node:fs 錯誤中的記憶體洩漏。Node.js 相容性改進,以及許多崩潰問題已修正。
  • v1.0.20 - 降低了 fs.readlinkfs.readFilefs.writeFilefs.statHTMLRewriter 中的記憶體使用量。修正了 setTimeout 在 Linux 上導致 CPU 使用率過高的回歸問題。HTMLRewriter.transform 現在支援字串和 ArrayBufferfs.writeFile()fs.readFile() 現在支援 hex & base64 編碼。Bun.spawn 顯示了程序使用的 CPU 和記憶體量。

安裝 Bun

curl
npm
brew
docker
curl
curl -fsSL https://bun.dev.org.tw/install | bash
npm
npm install -g bun
brew
brew tap oven-sh/bun
brew install bun
docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

升級 Bun

bun upgrade

在 Bun 中導入 sqlite 資料庫

您現在可以在 Bun 中導入 sqlite 資料庫。這使得在您的專案中開始使用 sqlite 更加簡單。

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

const {id} = db
  .query("SELECT id FROM users LIMIT 1")
  .get();

console.log(id); // 1

這也適用於 bun build --compile,這非常適合將小型資料庫部署到生產環境。這表示您可以將整個應用程式建置成單一可執行檔,然後將該可執行檔與您的資料庫檔案一起部署,並在開發與生產環境中使用不同的資料庫檔案。

bun build --compile ./my-app.ts
# => ./my-app
# On your remote machine:
./my-app

在內部,這大致等同於

import { Database } from "bun:sqlite";
const db = new Database("./my.db");

將 sqlite 資料庫嵌入到單一檔案可執行檔中

如果您的應用程式受益於嵌入到可執行檔本身,也支援此功能。要嵌入 sqlite 資料庫,請在導入屬性中傳遞 embed: "true"

import db from './my.db'
          with {
            type: "sqlite",
            // Embed the database into the executable
            embed: "true"
          };

const {id} = db
  .query("SELECT id FROM users LIMIT 1")
  .get();

console.log(id); // 1

現在您可以使用 bun build --compile 將您的應用程式建置成單一可執行檔,資料庫將會嵌入到可執行檔本身中。

bun build --compile ./my-app.ts
mv my.db /tmp/my.db # This isn't used in my-app anymore
./my-app # Since the database is embedded, it will work without the database file

您也可以將嵌入式 sqlite 資料庫與 bun build --target=bun 一起使用。在這種情況下,它會將資料庫檔案複製到輸出目錄,並從那裡導入。

bun build --target=bun ./my-app.ts --outdir=out
./out/my-app # Since the database is copied into the output directory, it will work without the database file

將 SQLite 升級至 v3.45.0

SQLite 3.45.0 新增了 JSONB 支援,這使得儲存和讀取 JSON 資料更快。我們已升級 Bun(在 Linux 上)以使用此版本的 SQLite。

使用 bun build --compile 嵌入 .node 檔案

您現在可以使用 bun build --compile 嵌入 NAPI (n-api) addons .node 檔案。這對於捆綁原生 Node.js 模組非常有用,例如 @anpi-rs/canvas

canvas.ts
build
canvas.ts
import { promises } from "fs";
import { join } from "path";
import { createCanvas } from "@napi-rs/canvas";

const canvas = createCanvas(300, 320);
const ctx = canvas.getContext("2d");

ctx.lineWidth = 10;
ctx.strokeStyle = "#03a9f4";
ctx.fillStyle = "#03a9f4";

// Wall
ctx.strokeRect(75, 140, 150, 110);

// Door
ctx.fillRect(130, 190, 40, 60);

// Roof
ctx.beginPath();
ctx.moveTo(50, 140);
ctx.lineTo(150, 60);
ctx.lineTo(250, 140);
ctx.closePath();
ctx.stroke();

const pngData = await canvas.encode("png"); // JPEG, AVIF and WebP are also supported

await promises.writeFile(join(__dirname, "simple.png"), pngData);
build
bun build --compile canvas.ts

然後您可以執行可執行檔

rm -rf node_modules # Not needed anymore
./canvas # => simple.png

針對 bun build --target=node 的錯誤修正

我們已修正 bun build --target=node 中的許多錯誤。

現在支援要求 Node.js 內建模組,例如 fspath

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

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

先前,此程式碼在執行時會失敗

bun build --target=node ./my-app.ts --outfile=app.mjs
node ./app.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 build --target=node ./my-app.ts --outfile=app.mjs
node ./app.mjs

還修正了許多其他錯誤,包括

  • 遺失了 __require 函式的無效程式碼消除

現在支援資源管理

我們已實作 資源管理 的打包器支援,該提案目前處於 TC39 stage 3 階段,並在 TypeScript 中可用。這是一個新功能,可讓您管理檔案控制代碼、資料庫連線和網路 socket 等資源。它類似於 C# 中的 using 關鍵字。

// in an async function:
async function * g() {
  await using handle = acquireFileHandle(); // async-block-scoped critical resource
} // async cleanup

// in a block in an async context:
{
  await using obj = g(); // block-scoped declaration
  const r = await obj.next();
} // calls finally blocks in `g` and awaits result

您可以在 Bun 中以及使用 bun build 在執行時使用此功能。我們已實作一個 polyfill,它將在 Bun、Node.js 和網頁瀏覽器中運作(基於 esbuild 的 polyfill)。

我們也在 JavaScriptCore 中實作了部分支援,包括 Symbol.disposeSymbol.asyncDisposeSuppressedError。我們尚未在 JavaScriptCore 中實作剖析器或 AST 支援,因為目前我們可以為此使用轉譯器。

感謝 @paperclover 實作此功能。

bun remove missing-pkg 不再發生錯誤

先前,如果未安裝套件,bun remove missing-pkg 會發生錯誤。現在它不會發生錯誤。這使行為與 npm 一致。

BunNPM
image
image

感謝 @kaioduarte 的貢獻。

Linux 上 TCP socket 速度提升 4%

在 Linux 上,透過 TCP socket 發送大量資料現在速度提升了 4%。我們減少了 event loop 滴答計時所涉及的系統呼叫次數。

之前(為了簡潔起見,移除了一些數字)

timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 2
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
sendto(fd: 15, buff: 0x7f9093666dc0, len: 1016697408, flags: NOSIGNAL) = 1571592
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 2

之後(為了簡潔起見,移除了一些數字)

epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 127574
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
sendto(fd: 15, buff: 0x456, len: 72939772, flags: DONTWAIT|NOSIGNAL) = 4321878
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 127574
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
sendto(fd: 15, buff: 0x456, len: 68617894, flags: DONTWAIT|NOSIGNAL) = 4321878
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288

將 HTTP 伺服器標頭限制從 50 提高到 100

我們已將 uWebSockets fork 升級到最新版本,隨之而來的是預設 HTTP 伺服器標頭限制的變更。它從 50 提高到 100。

import.meta.dirnameimport.meta.filename 支援

現在支援 import.meta.dirnameimport.meta.filename。它們是 import.meta.dirimport.meta.path 的別名。這對於與 Node.js 相容性非常有用,Node.js 最近發布了對這些功能的支援。

// /path/to/file.js

console.log(import.meta.dirname); // /path/to
console.log(import.meta.filename); // /path/to/file.js

fs/promises 中的 FileHandle 支援

先前,Bun 的 fs/promises 不支援 FileHandle 物件。現在它支援了。

import { promises } from "node:fs";
const filehandle = await promises.open("file.txt", "r");
const stat = await filehandle.stat();
await filehandle.close();

已實作 events.on

events.on 傳回一個 AsyncIterator,可讓您在事件發生時循環遍歷事件。

有些套件依賴此 API,現在 Bun 中支援了它。

import { on, EventEmitter } from "node:events";

const ee = new EventEmitter();

// Emit later on
process.nextTick(() => {
  ee.emit("foo", "bar");
  ee.emit("foo", 42);
});

for await (const event of on(ee, "foo")) {
  // The execution of this inner block is synchronous and it
  // processes one event at a time (even with await). Do not use
  // if concurrent execution is required.
  console.log(event); // prints ['bar'] [42]
}
// Unreachable here

感謝 @nektro 實作此功能。

process.binding('tty_wrap') 支援

現在支援 process.binding('tty_wrap')。某些套件(例如 readline-sync)依賴此 API,即使 bun 的 tty 實作與 Node.js 不同,我們也實作了此 Node.js 內部綁定 API,以支援依賴它的套件。

process.binding("tty_wrap").TTY;

已實作 fs.fdatasync

現在已實作 fs.fdatasync,感謝 @nektro

已修正:來自 zlibBufferSync 錯誤的「Not a string or buffer」

我們的 zlibBufferSync polyfill 未正確檢查非 Buffers,例如 Uint8Array。此問題已修正,感謝 @Electroid

已修正:Bun.spawn() 與 IPC 中的檔案描述器洩漏

當使用 IPC 時,Bun.spawn() 會忽略關閉第二個 socket 的檔案描述器,導致檔案描述器洩漏。此問題已修正。

已修正:以與 node 一致的方式處理 node:url 中遺失的 .host

以下程式碼片段在 Bun 中的行為與 Node.js 中的行為不同

const url = require("url");

console.log(url.parse("http://"));

此問題已修正,感謝 @kaioduarte

已提升 Node.js 版本

process.versions.node 中報告的 Node.js 版本已提升至 v21.6.0,這是發布時的最新版本。

已修正:peer dependency 升級解析

假設您安裝了 drizzle-clidrizzle-ormdrizzle-clidrizzle-orm@^1.0.0 有 peer dependency。您將 drizzle-orm 升級到 1.0.1,您期望 drizzle-cli 現在使用 drizzle-orm@1.0.1。先前,這不會發生 -- 但現在會發生了,感謝 @eriklangille

已修正:semver 比較錯誤

node-semver 相比,以下測試在 Bun.semver 中會表現出不同的行為

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

test("semver with multiple tags work properly", () => {
  expect(semver.satisfies("3.4.5", ">=3.3.0-beta.1 <3.4.0-beta.3")).toBeFalse();
});

此錯誤影響了 bun installBun.semver

此問題已修正,感謝 @Electroid

已修正:WebSocket 在連線失敗時拋出錯誤

以下程式碼在 Bun 中會拋出錯誤,但在網頁瀏覽器中則不會

const ws = new WebSocket("wss://apsodkapsodkpo.com:3000");
// => Uncaught Error:

相反地,它應該發出錯誤事件。此問題已修正,感謝 @LukasKastern

已修正:無效的 stdio 選項 "null"

以下程式碼在 Bun 與 Node.js 中的行為會有所不同

import cp from "child_process";
cp.spawnSync("ls", ["-lagh"], { stdio: [null, "inherit", null] });

此問題已修正,感謝 @Electroid

已修正:Bun.serve() 有時會重複印出例外狀況

已修正以下程式碼會重複印出例外狀況的錯誤

Bun.serve({
  port: 3000,
  fetch: async () => {
    await Bun.sleep(1);
    throw new Error("A");

    return new Response("A");
  },
  error() {
    return new Response("Handled");
  },
});

此問題已修正,感謝 @LukasKastern

已修正:ServerWebSocket idleTimeout

已修正一個 event loop 錯誤,該錯誤可能導致 ServerWebSocket timeout 花費的時間比預期的要長得多。

感謝 @LukasKastern 修正此問題。

已修正:macOS 大型檔案上傳有時會失敗

已修正一個影響 macOS 的錯誤,其中一次接收到的最終大型檔案上傳區塊(> 512 KB)有時會遺失最終區塊的一部分。此錯誤是由於 KQueue 事件的處理不正確所致。與 Linux (epoll) 不同,kqueue 可能同時發送 socket hangup 和 readable 事件,當資料大於讀取緩衝區時,我們未正確處理這種情況。

感謝 12 位促成此版本發布的貢獻者!

完整變更日誌:https://github.com/oven-sh/bun/compare/bun-v1.0.22...bun-v1.0.23