Bun

Bun v0.8.1


Jarred Sumner · 2023 年 8 月 26 日

Bun v0.8.1 為 Bun.serve() 新增了 unix domain socket 支援,修復了效能衰退問題,並修正了 bun install、node:http 和 napi 中的錯誤。

Bun 1.0 即將在 9 月 7 日推出!請至 https://bun.dev.org.tw/1.0 註冊觀看發佈直播。

Bun 是一個速度極快的 JavaScript 執行時、打包器、轉譯器和套件管理器 — 功能All-in-One。我們最近發佈了許多 Bun 的變更。以下是最近幾個版本的重點回顧。In case you missed it

  • v0.7.0 - Web Workers、--smolstructuredClone()、WebSocket 可靠性改進、node:tls 修正及更多。
  • v0.7.1 - ES Modules 載入速度提升 30-250%、fs.watch 修正及許多 node:fs 相容性改進。
  • v0.7.2 - 實作了 node:worker_threadsnode:diagnostics_channelBroadcastChannel
  • v0.7.3 - bun test 中的覆蓋率報告,以及使用 bun test -t 進行測試篩選。
  • v0.8.0 - 偵錯器支援、fetch 串流和 bun update。實作了 node:tty 中的 ReadStream 和 WriteStream,包括 process.stdin 上的原始模式。SvelteKit 支援

安裝 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.serve() 在 unix domain socket 上啟動 HTTP 伺服器

Bun.serve() 現在支援 unix domain sockets,可讓您將 socket 指向檔案系統上的檔案,而不是網路主機/port。當您想要執行僅能從同一部機器存取的伺服器(有時是容器或代理伺服器)時,這非常有用。

server.ts
const server = Bun.serve({
  unix: "/tmp/my-socket.sock", // <-- new option
  fetch(req){
    console.log(req.url);
    return new Response("Hello world!");
  }
});

console.log(`Listening on unix:///tmp/my-socket.sock!`);

若要啟動伺服器,請執行 bun ./server.ts

bun ./server.ts
Listening on unix:///tmp/my-socket.sock!

然後,您可以使用 curl 向 socket 發出請求。

curl --unix-socket /tmp/my-socket.sock https://127.0.0.1/my-path
Hello world!

修復了讀取請求body時的效能衰退問題

Bun v0.8.0(昨天發佈)在讀取請求 body 時引入了效能衰退問題。此問題已在 v0.8.1 中修復。

以下腳本

Bun.serve({
  port: 3000,
  async fetch(request) {
    await request.json();
    return new Response();
  },
});

Bun v0.8.1

❯ oha http://127.0.0.1:3000 -m POST -d '{"a": 123}' -n 200000
Summary:
  Success rate:	1.0000
  Total:	1.8099 secs
  Slowest:	0.0068 secs
  Fastest:	0.0000 secs
  Average:	0.0005 secs
  Requests/sec:	110505.0574

Bun v0.8.0

❯ oha http://127.0.0.1:3000 -m POST -d '{"a": 123}' -n 200000
Summary:
  Success rate:	1.0000
  Total:	5.1787 secs
  Slowest:	0.0121 secs
  Fastest:	0.0002 secs
  Average:	0.0013 secs
  Requests/sec:	38619.4133

效能衰退了 2.8 倍!

問題在於 microtask 排程。

Microtask 排程

在 Bun v0.8 中,我們修正了 HTTP 伺服器和 sockets 的事件迴圈排程中長期存在的效率低下的問題,但遺漏了一個導致此效能衰退的案例。

JavaScript 的事件迴圈有兩種任務類型:microtasks 和 tasks。

TaskMicrotask
setTimeoutsetInterval、async I/OqueueMicrotaskPromise.resolveprocess.nextTick

Microtasks 使用 queueMicrotask()Promise.resolve()process.nextTick 和一些其他 API 進行排程。

Tasks 使用 setTimeout()setInterval() 或 async I/O(例如 HTTP 伺服器)進行排程。Tasks 使程序保持運作,而 microtasks 在每個 task 結束時清空。

您可以將 JavaScript 的事件迴圈視為更複雜的版本

let task;
while ((task = getNextTask())) {
  task();

  do {
    let microtask;
    while ((microtask = getNextMicrotask())) {
      microtask();
    }
  } while (hasMicrotasks());
}

每個 task 都可能排程更多 microtasks 和 tasks。Microtasks 在每個 task 結束時清空,而 tasks 在每個事件迴圈迭代結束時清空。

Bun 的事件迴圈先前更像是這樣運作

while (true) {
  let task;
  while ((task = getNextTask())) {
    task();

    do {
      let microtask;
      while ((microtask = getNextMicrotask())) {
        microtask();
      }
    } while (hasMicrotasks());
  }

  while ((task = getAsyncIOTask())) {
    task();

    // where's the microtask draining? 🤔
  }
}

對於 async IO,我們沒有清空 microtasks!這導致了許多不必要的 microtask 排程,這在某些情況下導致過多的記憶體使用和延遲。 案例。

但這並不是 v0.8.0 之後效能衰退的原因。

效能衰退的原因是我們忘記在 讀取請求 body 時清空 microtasks。

這表示對於每個請求 body,我們都會排程一個 microtask 來讀取 body,然後僅在稍後排程下一個 task 時才清空它。 排程。在 < Bun v0.8.0 中,HTTP 伺服器中建立的 microtasks 會在 所有其他 tasks 完成後才清空。這表示遺漏 microtask 清空 在任何地方都是一致的。在 Bun v0.8.0 中,HTTP 伺服器中建立的 microtasks 在當前請求完成後清空,這表示在此案例(讀取請求 body)中跳過它會 變得不平衡。這導致了效能衰退。 效能衰退。

bun install 錯誤修正:版本名稱格式錯誤

^0.0.2rc1 版本規範無效,但在 npm 中實際存在。先前,Bun 在收到此輸入時會崩潰,此問題已修正。

靜態已知的失敗 require 錯誤地在執行時內聯

當 Bun 的打包器知道 require() 呼叫在執行時會失敗,並且位於 try/catch 區塊內時,會自動內聯失敗的 require() 呼叫。這對於打包使用選用相依性的程式碼非常有用。

輸入

try {
  require("i-dont-exist-but-thats-okay");
} catch (e) {
  console.log("I don't exist, but that's okay!");
}

輸出

try {
  (() => {
    // the bug! it shouldn't be inlined here. it should only be inlined when bundling.
    throw new Error(`Cannot require module "i-dont-exist-but-thats-okay"`);
  })();
} catch (e) {
  console.log("I don't exist, but that's okay!");
}

此功能錯誤地在執行時啟用,而不僅僅是在打包時啟用。這 破壞了一些依賴於檢查模組是否存在或 檢查 Node.js 公開的特定 code 屬性的套件。此問題已 修正。Bun 的執行時不再內聯失敗的 require() 呼叫(打包器仍然會,這是正確的)

此錯誤主要影響 napi。

當存在大量 Headers 和 Blob 物件時,減少記憶體使用量

Bun 的 HeadersBlob 實作未將其大小報告給 垃圾回收器。由於 Bun 在原生程式碼中實作了許多類別,因此垃圾 回收器並不總是能看到類別實際使用了多少記憶體。 HeadersBlob 夠大時,這可能會導致垃圾回收器 無法像應有的頻率那樣執行。

現在 Bun 會將 HeadersBlob 的大小報告給垃圾回收器。

fetch() 記憶體報告錯誤修正

目前,每次呼叫 fetch() 約使用 3 KB 的記憶體。此記憶體 在原生程式碼中使用,這表示垃圾回收器無法看到它。現在 Bun 會將此記憶體報告給垃圾回收器,這表示垃圾 回收器在有大量 fetch() 呼叫時將更頻繁地執行。

bun install 錯誤修正:package.json 腳本過時

bun install 的 lockfile 是二進位檔,這讓我們可以在 lockfile 中儲存比平常更多的資料。我們儲存的其中一個項目是 package.json 腳本。 先前,如果您執行 bun install,然後變更 package.json腳本,bun install 並非總是會擷取變更。此問題已 修正。

Sourcemap 錯誤修正在 bun --inspect

由於 Bun 會轉譯每個檔案,因此 Bun 也必須為每個檔案保留 sourcemap。 這用於使 Error.prototype.stack 產生 sourcemapped stacktraces console.log 報告準確的行號。

不幸的是,Sourcemaps 非常耗用記憶體。我們沒有將整個 sourcemap 儲存在記憶體中,而是儲存更精簡的版本,直到首次使用。精簡 版本的前 24 個位元組包含額外的元資料(例如,原始輸入原始碼的 行數)。此 24 位元組標頭錯誤地包含在 bun --inspect 中使用的內聯 sourcemaps 中, 導致 JSON sourcemap 的前 24 個位元組左右的輸入無效。 令人驚訝的是,這並非總是會破壞 sourcemaps。只有有時會。 無論如何,此問題已修正,現在 Bun 會如預期般包含 sourcemap 位元組, 預期般包含 sourcemap 位元組,而沒有額外的元資料。 預期般包含 sourcemap 位元組,而沒有額外的元資料。

Proxy URL 與 node:http 錯誤已修正

當使用設定了 http_proxy 環境變數執行以下腳本時,會擲回錯誤

腳本

import axios from "axios";

const res = await axios.get("https://httpbin.org/get?answer=42");

console.log(res.data.args);

執行

http_proxy=http://127.0.0.1:1087 https_proxy=http://127.0.0.1:1087 bun run index.ts

錯誤

TypeError: fetch() URL is invalid
      at node:http:839

不應該發生此錯誤。感謝 @Hanaasagi,此問題已修正。