Bun

Bun v1.1.31


Ashcon Partovi · October 18, 2024

我們正在舊金山招募系統工程師,一同打造 JavaScript 的未來!

此版本修正了 41 個錯誤(解決了 595 個 👍)。它包含了 node:http2 伺服器和 gRPC 伺服器支援、bun install 中的 ca 和 cafile 支援、Bun.inspect.table、bun build --drop、Promise.try、Buffer.copyBytesFrom、可迭代的 SQLite 查詢、迭代器輔助方法,以及多項 Node.js 相容性改進和錯誤修正。

安裝 Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.dev.org.tw/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install 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

支援 node:http2 伺服器和 gRPC

在此 Bun 版本中,我們新增了對我們最受歡迎的功能請求的支援:HTTP2 伺服器和 gRPC 支援。Bun 自 v1.0.13 以來一直支援 node:http2 用戶端,但直到現在我們才支援伺服器。

在 Bun 中,node:http2 的執行速度比 Node v23 快 2.4 倍。

您可以使用 node:http2 API 來建立 HTTP2 伺服器。

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

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

server.on("error", (error) => console.error(error));
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);

有了 HTTP2 支援,您也可以將 gRPC 與 @grpc/grpc-js 等套件一起使用。

const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDefinition = protoLoader.loadSync("benchmark.proto", {});
const proto = grpc.loadPackageDefinition(packageDefinition).benchmark;
const fs = require("fs");

function ping(call, callback) {
  callback(null, { message: "Hello, World" });
}

function main() {
  const server = new grpc.Server();
  server.addService(proto.BenchmarkService.service, { ping: ping });
  const tls =
    !!process.env.TLS &&
    (process.env.TLS === "1" || process.env.TLS === "true");
  const port = process.env.PORT || 50051;
  const host = process.env.HOST || "localhost";
  let credentials;
  if (tls) {
    const ca = fs.readFileSync("./cert.pem");
    const key = fs.readFileSync("./key.pem");
    const cert = fs.readFileSync("./cert.pem");
    credentials = grpc.ServerCredentials.createSsl(ca, [
      { private_key: key, cert_chain: cert },
    ]);
  } else {
    credentials = grpc.ServerCredentials.createInsecure();
  }
  server.bindAsync(`${host}:${port}`, credentials, () => {
    console.log(
      `Server running at ${tls ? "https" : "http"}://${host}:${port}`,
    );
  });
}

main();

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="..."

bun build --drop

您可以使用 --drop 從您的 JavaScript 捆綁包中移除函式呼叫。如果您想要從您的生產捆綁包中移除偵錯程式碼,這非常有用。例如,如果您傳遞 --drop=console,則所有對 console.log 的呼叫都將從您的程式碼中移除。

JavaScript
CLI
JavaScript
await Bun.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 --drop

--drop 也可在執行時使用。

index.ts
for (let i = 0; i < 3; i++) {
  console.log("hello called!");
}

在使用 --drop 之前,您會看到 hello called! 3 次。

bun ./index.ts
hello called!
hello called!
hello called!

使用 --drop=consoleconsole.log 呼叫會被捨棄,而您不再看到 hello called!

bun --drop=console ./index.ts
# no output

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 │
// └───┴───┴───┴───┘

您也可以傳遞屬性名稱陣列,以僅顯示屬性的子集。

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

// ┌───┬───┬───┐
// │   │ a │ c │
// ├───┼───┼───┤
// │ 0 │ 1 │ 3 │
// │ 1 │ 4 │ 6 │
// └───┴───┴───┘

如果您想要啟用 ANSI 色彩,您可以在選項物件上設定 colors 屬性。

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

可迭代的 SQLite 查詢

Bun 具有內建的 SQLite API,可讓您輕鬆查詢 SQLite 資料庫。現在您可以傳回一個迭代器,它會產生從資料庫傳回的列,而不是傳回列陣列。

import { Database } from "bun:sqlite";

const sst = new Database(":memory:");
sst.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)");

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

  get nameAndEmail() {
    return `${this.name} <${this.email}>`;
  }
}

const rows = sst
  .query("SELECT * FROM users")
  .as(User)
  .iterate() // <--- here
  // call the .nameAndEmail property on each row
  .map((row) => row.nameAndEmail);

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

// prints nothing because sst has no users

您也可以在查詢上呼叫 .iterate() 方法,以取得一個迭代器,它會產生從資料庫傳回的列。

for (const row of db.query("SELECT * FROM users").iterate()) {
  console.log(row); // { id: 1, name: "Bun" }
}

感謝 @Skywalker13 實作此功能!

迭代器輔助方法

在此 Bun 版本中,有新的 API 可以更輕鬆地使用 JavaScript 迭代器和生成器。這些 API 是在 TC39 提案中引入的,現在已在 Safari 和 Chrome 中提供。感謝 WebKit 團隊實作這些 API,特別是 @sosukesuzuki@shvaikalesh

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

Promise.try

Promise.try 方法類似於 Promise.resolve,但它也適用於同步函式。

Promise.try(() => {
  return 1 + 1;
}).then((result) => {
  console.log(result); // 2
});

這是一個 stage4 TC39 提案(即,它已從提案升級到標準化 ECMAScript)。感謝 @rkirsling 實作此功能!

Error.captureStackTrace 速度提升 9 倍

expect(fn).toThrow() 在未擲回錯誤時列印傳回值

之前

image

現在

image

感謝 @nektro 實作此功能!

在首次閒置請求逾時時發出警告

新錯誤:JSX 關閉標籤不符

先前,即使以下程式碼是無效的 JSX,也不會擲回錯誤

const Oopsie = () => {
  return (
    <h1>
      That should be an h1! Not a div.
    </div>
  );
};

我們應該一直在此處擲回錯誤,但我們沒有。現在我們做到了

❯ bun build Oopsie.tsx
5 |     </div>
          ^
error: Expected closing JSX tag to match opening tag "<h1>"
    at Oopsie.tsx:5:7

3 |     <h1>
         ^
note: Opening tag here:
   at Oopsie.tsx:3:6

感謝 @DonIsaac 修正此問題!

Node.js 相容性

新增:Buffer.copyBytesFrom(view[, offset[, length]])

您現在可以使用 Buffer.copyBytesFrom 方法,透過從型別陣列或 DataView 複製位元組來建立新的 Buffer

const u16 = new Uint16Array([0, 0xffff]);
const buf = Buffer.copyBytesFrom(u16, 1, 1);
u16[1] = 0;
console.log(buf.length); // 2
console.log(buf[0]); // 255
console.log(buf[1]); // 255

感謝 @nektro 實作此功能!

已修正:fs.open()

我們修正了 fs.open() 中的各種問題,使其更符合 Node.js 的行為。感謝 @nektro 修正此問題!

已修正:Node-API 錯誤

我們一直在努力修正 Bun 中 Node-API 實作的錯誤。感謝 @190n 修正這些錯誤!

  • napi_create_empty_array 在陣列過大時不會崩潰
  • napi_create_empty_array 使用空插槽,而不是 undefined
  • napi_get_value_int32 使用與 JavaScript 中相同的位元運算轉換
  • napi_get_value_int64 現在符合 Node.js 的行為
  • napi_throw_errornapi_create_error 現在已測試,且有時不會崩潰

已修正:設定 UV_THREADPOOL_SIZE

有些 Node.js 套件會設定 UV_THREADPOOL_SIZE 環境變數,以控制 libuv 使用的執行緒數。雖然 Bun 僅在 Windows 上且僅針對某些 API 使用 libuv,但現在它會尊重此環境變數。

已修正:Buffer.from JSON 序列化

當您在 Buffer 上呼叫 JSON.stringify 時,它會將 {type: "Buffer", value: [...buffer contents]} 新增至 JSON。這已經可以運作,但 Buffer.from 不會接受此 JSON。現在可以了。

const buf = Buffer.from([1, 2, 3]);
JSON.stringify(buf); // "{"type":"Buffer","data":[1,2,3]}"
Buffer.from(JSON.parse('{"type":"Buffer","data":[1,2,3]}'))[0] === 1; // works

先前,這會擲回錯誤

TypeError: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.
      at [file]:3:20

感謝 @nektro 修正此問題!

已修正:node:child_process.SpawnResult .stderr.stdio 不是 "pipe" 時應為 null

import child_process from "node:child_process";

const res = child_process.spawn("ls", {
  stdio: "inherit",
});
console.log(res.stderr); // now 'null'

這解鎖了 tinyexec 套件的使用。

感謝 @nektro 修正此問題!

錯誤修正

已修正:具有數字鍵的 console.table 格式

我們修正了一個錯誤,當鍵為數字時,console.table 會以不正確的間距格式化。感謝 @pfgithub 修正此問題!

console.table({test: {"10": 123, "100": 154}});

// Before
┌──────┬─────┬─────┐
│      │ 10100
├──────┼─────┼─────┤
│ test │     │     │
└──────┴─────┴─────┘

// After
┌─────────┬─────┬─────┐
│ (index) │ 10100
├─────────┼─────┼─────┤
│ test    │ 123154
└─────────┴─────┴─────┘

已修正:使用 Bun.color 時崩潰

在最近的版本中,我們推出了 Bun.color,這是一個 API,可讓您剖析和轉換不同格式的色彩,包括 css、ansi、hex、rgb、hsl 等。

我們修正了一個錯誤,如果您沒有傳遞格式字串,Bun.color 會崩潰。

已修正:instanceof 的回歸

透過 WebKit 升級在 Bun v1.1.30 中引入的 instanceof 回歸已修正。錯誤可能是由 JavaScript 程式碼將 instanceof 的輸出值儲存在與輸入相同的變數中所導致。錯誤訊息看起來會像

error: [BUG] Unreachable
      at ... (input.js:2247:47)

此錯誤最常在使用啟用來源地圖的 sass 時看到。

感謝 @hyjorc1 修正!

已修正:使用 using 和生成器的轉譯器錯誤

我們修正了一個錯誤,其中 using 語句之前的變數宣告會導致轉譯器將生成器提升到 using 宣告的範圍之外。這會導致 ReferenceError,但現在已感謝 @snoglobe 修正。

已修正:bun install 的檔案權限

有些 npm 套件發佈時具有不正確的檔案權限,我們在 Bun 中新增了一個檢查,以確保從 npm 套件解壓縮的檔案至少是可讀的。這與 npm install 的行為相符。

已修正:bun publish 的中繼資料發佈遺失

修正了一個錯誤,其中 bun publish 在傳送發佈請求時,遺失了重要的套件中繼資料,包括來自 bindirectories.bin 的相依性和二進位檔。這會導致發佈的套件由於 node_modules/.bin 中缺少過渡性相依性和二進位檔而無法正確安裝。

已修正:fs.mkdir 的空字串引數

已修正將空字串傳遞給 fs.mkdir 所造成的整數溢位。這只會在 recursive 選項設定為 true 時發生。

const fs = require("fs");
fs.mkdirSync("", { recursive: true }); // throws ENOENT

已修正:fetch keepalive 選項

先前已忽略 fetchkeepalive 選項。現在它預設會停用傳送 "Connection: keep-alive" 標頭。

const res = await fetch("https://example.com", { keepalive: false });
console.log(res.headers.get("connection")); // "close"

感謝 19 位貢獻者!