Bun

Bun v0.6.8


Colin McDonnell · June 9, 2023

我們正在招募 C/C++ 和 Zig 工程師,一同打造 JavaScript 的未來! 加入我們的團隊 →

我們最近為 Bun 發布了很多變更,這裡為您總結一下,以防您錯過任何資訊

  • v0.6.0 - 介紹 bun build,Bun 的全新 JavaScript 打包器。
  • v0.6.2 - 效能提升:JSON.parse 速度提升 20%,Proxyarguments 速度提升高達 2 倍。
  • v0.6.3 - 實作 node:vm,修復了 node:httpnode:tls 的許多問題。
  • v0.6.4 - 實作 require.cacheprocess.env.TZ,以及 bun test 速度提升 80%。
  • v0.6.5 - 原生支援 CommonJS 模組 (先前,Bun 執行 CJS 到 ESM 的轉譯),
  • v0.6.6 - bun test 改善,包括 Github Actions 支援、test.only()test.if()describe.skip(),以及超過 15 個以上的 expect() 匹配器;同時也包含使用 fetch() 的串流檔案上傳。
  • v0.6.7 - Node.js 相容性改進,以解除對 Discord.js、Prisma 和 Puppeteer 的封鎖

這個版本實作了頂層的 Bun.password API,用於安全的密碼雜湊、函式模擬與 bun test 中的 toMatchObject,以及 Bun.serve() 中實驗性的 inspector 模式,此外還有一些錯誤修正和穩定性改進。我們也對 bun:sqlite 進行了破壞性變更。

安裝 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.password

Bun 現在公開了 Bun.password API,以便於以密碼學安全的方式雜湊密碼。

const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4

雜湊演算法及其參數會編碼到傳回的雜湊值中,因此可以使用 Bun.password.verify 驗證雜湊值,而無需提供額外的元數據。

const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password);
const isMatch = await Bun.password.verify(password, hash);
// => true

預設演算法是 argon2,但也支援 bcrypt。兩種演算法都可以根據需要進行配置。

const password = "super-secure-pa$$word";

// use argon2 (default)
const argonHash = await Bun.password.hash(password, {
  algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d"
  memoryCost: 4, // memory usage in kibibytes
  timeCost: 3, // the number of iterations
});

// use bcrypt
const bcryptHash = await Bun.password.hash(password, {
  algorithm: "bcrypt",
  cost: 4, // number between 4-31
});

當使用 bcrypt 時,傳回的雜湊值會以 模組化加密格式 編碼,以與大多數現有的 bcrypt 實作相容;而 argon2 的結果則以較新的 PHC 格式 編碼。在底層,Bun.password 使用了 Zig 標準函式庫中的 std.crypto.pwhash 模組。

請參閱 文件 > 雜湊 > Bun.password 以取得完整文件。

bun test 中的函式模擬

Bun 的測試執行器現在支援函式模擬以及一些相關的 expect() 匹配器。

import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());

test("random", async () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});

mock() 的結果是一個新的函式,它被裝飾了一些額外的屬性和方法。

模擬
import { mock } from "bun:test";
const random = mock((multiplier: number) => multiplier * Math.random());

random(2);
random(10);

random.mock.calls;
// [[ 2 ], [ 3 ]]

random.mock.results;
//  [
//    { type: "return", value: 0.6533907460954099 },
//    { type: "return", value: 0.6452713933037312 }
//  ]

bun:test 中建立模擬的開銷很低且速度很快。在建立 100,000 個模擬的基準測試中,bun:test 比 Jest 快 80 倍,比 Vitest 快 100 倍。

jest.spyOn 支援

也新增了對 Jest 的 spyOn 功能的支援。這提供了一種追蹤和內省方法調用的機制。

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

const ringo = {
  name: "Ringo",
  sayHi() {
    console.log(`Hello I'm ${this.name}`);
  },
};

const hiMock = spyOn(ringo, "sayHi");

test("spyon", () => {
  expect(hiMock).toHaveBeenCalledTimes(0);
  ringo.sayHi();
  expect(hiMock).toHaveBeenCalledTimes(1);
});

為了方便遷移,bun:test 導出了一個用於 jest 全域物件的 polyfill。這個 polyfill 實作了 jest.fnjest.spyOn,適用於已經依賴 Jest 風格語法的程式碼庫。

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

const mockFn = jest.fn(() => {});

const mockMethod = jest.spyOn(
  {
    random() {
      return Math.random();
    },
  },
  "random",
);

已新增對 .toHaveBeenCalled().toHaveBeenCalledTimes() 匹配器的支援。歡迎貢獻者提交 PR 以新增其他匹配器,例如 .toHaveBeenCalledWith().toHaveBeenLastCalledWith()

我們尚未新增對模組模擬的支援。請繼續關注未來的版本,但我非常確定我們可以使用相同的 API 使其適用於 CommonJS 和 ES Modules。

toMatchObject

toMatchObject 匹配器已新增至 bun:test。此匹配器對於斷言物件是否包含屬性的子集很有用。

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

test("toMatchObject", () => {
  const obj = {
    a: 1,
    b: 2,
    c: 3,
  };

  expect(obj).toMatchObject({
    a: 1,
    b: 2,
  });
  expect(obj).not.toMatchObject({
    c: 4,
  });
});

您也可以將 toMatchObject 與非對稱匹配器一起使用,例如 expect.stringContainingexpect.any(Number)

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

test("toMatchObject", () => {
  const obj = {
    a: "hello",
    b: "world",
  };

  expect(obj).toMatchObject({
    a: expect.stringContaining("ell"),
    b: expect.any(String),
  });
});

(破壞性變更)bun:sqlite.values() 方法現在傳回 [] 而不是 null

之前,由於沒有匹配的列,以下程式碼會傳回 null

db.query("SELECT * FROM foo WHERE id > 9999").values();
// null

從 Bun v0.6.8 開始,它會傳回 []

db.query("SELECT * FROM foo WHERE id > 9999").values();
// []

這與其他函式和其他 SQLite3 函式庫的行為一致

db.query("SELECT * FROM foo WHERE id > 9999").all(); // []
db.query("SELECT * FROM foo WHERE id > 9999").values(); // [] (previously: null)

我們進行了一項 Twitter 投票,75% 的人投票贊成這項破壞性變更。

Bun.serve 中的實驗性 inspector 模式

Bun 的 HTTP 伺服器現在提供「inspector」模式。要啟用它,請設定 inspector: true。設定 NODE_ENV=production 會停用 inspector 模式,無論傳遞給 Bun.serve 的值為何。

Bun.serve({
  inspector: true,
  fetch(req) {
    // handle request
  },
});

這會在 localhost:$PORT/bun:inspect 掛載一個基於 WebSocket 的除錯器,它可以處理符合 WebKit 除錯器協定的訊息。這是朝向在使用 Bun 開發全端應用程式時,更進階的瀏覽器端除錯體驗邁出的第一步。

這個 gist 提供了一個簡單但功能完整的瀏覽器內「REPL」實作,它可以執行伺服器端程式碼並透過 WebSockets 檢索結果。

一個簡單的線上記 REPL。

為了更輕鬆地開發針對 WebKit 除錯器協定的工具,我們已將 bun-devtools 發布到 npm,其中提供了協定的操作、日誌等的自動生成的 Typescript 類型。

import { JSC } from "bun-devtools";

轉譯器與打包器錯誤修正

我們修正了打包器和轉譯器中的一些問題

lodash-es/isBuffer 打包失敗

由於 CommonJS 和 ES Module 互操作性的邊緣情況,lodash-es 導出的 isBuffer 無法運行。

以下程式碼來自 lodash-es/isBuffer.js

import root from "./_root.js";
import stubFalse from "./stubFalse.js";

var freeExports =
  typeof exports == "object" && exports && !exports.nodeType && exports;

var freeModule =
  freeExports &&
  typeof module == "object" &&
  module &&
  !module.nodeType &&
  module;

var moduleExports = freeModule && freeModule.exports === freeExports;
var Buffer = moduleExports ? root.Buffer : undefined;
var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;

var isBuffer = nativeIsBuffer || stubFalse;

export default isBuffer;

moduleexportstypeof 檢查是不正確的,因為模組始終是 ES Module,因此 moduleexports 始終未定義。CommonJS 識別符的使用導致 Bun 將其視為 CommonJS 模組,從而導致運行時異常。這個問題已修正。

然而,lodash-es 中的 isBuffer 函式在 Node.js 和 Bun 中仍然損壞(它始終傳回 false)。我們已向 lodash-es 提交了一個 issue。

標籤模板字面量中的 \r\n 正規化

在某些情況下,Bun 會錯誤地正規化標籤模板字面量中的 Windows 換行符。這個問題已修正。

RegExp 中的 v flag

當在 RegExp 字面量中使用 v flag 時,Bun 會錯誤地拋出語法錯誤。v flag 是 stage4 TC39 提案

bun install 工作區生命週期腳本錯誤修正

bun install 現在為工作區套件運行生命週期腳本。以前,僅運行根套件的生命週期腳本。

感謝 @alexlamsl 的修正!

console.log(myBuffer) 顯示 Buffer 而不是 Uint8Array

之前,對於 Buffer 實例,console.log 會顯示 Uint8Array 而不是 Buffer。這個問題已修正。

const buf = Buffer.from("hello world");
console.log(buf);

之前

Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]

現在

Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]

更新日誌

#3209[轉譯器] 修正標籤樣板字串字面值中 \r\n 的正規化,作者:@Jarred-Sumner
#3208removeAllListeners 傳回 this,作者:@dylan-conway
#3204實作 Bun.passwordBun.passwordSync,作者:@Jarred-Sumner
#3215[轉譯器] 修正原始模板內容的新長度,作者:@dylan-conway
#3213允許 regexp 字面值中使用 v flag,作者:@dylan-conway
#3220console.log(Buffer.from('a')) -> Buffer(1) [... 而不是 Uint8Array(1) [...,作者:@Jarred-Sumner
#3219[破壞性變更][bun:sqlite] .values() 傳回 [] 而不是 null,作者:@Jarred-Sumner
#3254在 GitHub Actions 中替換 sudo 用法,作者:@alexlamsl
#3231修正在移除套件後保留換行符號的問題,作者:@ytakhs
#3252在 bun:test 中實作模擬,作者:@Jarred-Sumner

查看完整更新日誌