Bun

Bun v1.1.21


Ashcon Partovi · July 27, 2024

Bun v1.1.21 版本發布!此版本修正了 72 個錯誤(解決了 63 個 👍)。fetch() 解壓縮速度提升 30%,新增 --fetch-preconnect 標誌,改進了 Remix 支援,Bun 在 Linux 上縮小了 4 MB,捆綁套件時排除依賴項,許多捆綁器修復和 Node 相容性改進。

我們正在舊金山招聘系統工程師,以構建 JavaScript 的未來!

先前的版本

  • v1.1.20 & v1.1.19 修正了 54 個錯誤(解決了 248 個 👍)。JavaScript 在 Windows 上速度更快。Raspberry Pi 4 支援。.npmrc 中支援 _auth。bun install 保留 package.json 縮排。aws-cdk-lib 支援。修復了 new Response(request)fs.readdir 中的記憶體洩漏問題。多項可靠性改進。
  • v1.1.18 修正了 55 個錯誤(解決了 493 個 👍)。支援 npmrc。改進了常數折疊和枚舉內聯。改進了函數的 console.log 輸出。修復了工作區中修補依賴項的問題。修復了 bun install 在連結二進制檔案時的幾個錯誤。修復了 dns.lookup 中的崩潰,並標準化了 DNS 錯誤以更好地匹配 Node。修復了 Bun.escapeHTML 中的斷言失敗。升級了 JavaScriptCore。
  • v1.1.0 Bundows。Windows 支援來了!

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

fetch() 解壓縮速度提升 30%

由於 libdeflate,在 Linux x64 上解壓縮使用 gzip 或 deflate 壓縮的 fetch() 響應正文速度提升了 30%。

我們還在 websocket 伺服器中啟用了 libdeflate,以加快解壓縮小型訊息的速度。

新增:--fetch-preconnect 提前預熱連線

我們新增了一個新的標誌 --fetch-preconnect=<url> <file>,它會在執行任何程式碼之前啟動對指定 URL 的 HTTP 請求。

bun --fetch-preconnect='https://example.com' ./index.ts

對於許多生產服務來說,Bun 腳本執行的第一件事是使用 fetchnode:http 向伺服器發送 HTTP 請求。許多網路請求中最慢的部分是初始連線設定,這可能涉及 DNS 查找、TCP 連線建立和 TLS 協商。有時這需要 100 毫秒才能發送 HTTP 請求。

這項工作受網路限制。如果可以在執行任何程式碼之前啟動該連線,以便在其餘程式碼運行時,連線已經建立,那會怎麼樣?這就是 --fetch-preconnect 的作用。它很像 HTML 中的 <link rel="preconnect">,但適用於伺服器端 JavaScript。

在此過程中,我們還新增了一個 JavaScript API 來執行相同的操作

fetch.preconnect(url: string): void;

這對於無伺服器環境和在 fetchnode:http 之上運行的資料庫非常有用。

 bun --fetch-preconnect=https://example.com ./hey.mjs
[8.37ms] fetch(https://example.com)

❯ bun hey.mjs # Without preconnect
[51.92ms] fetch(https://example.com)

❯ node hey.mjs
fetch(https://example.com): 68.417ms

❯ cat hey.mjs
       │ File: hey.mjs
   1if (globalThis.Bun) {
   2   │   await Bun.sleep(1000);
   3   │ } else {
   4   │   await new Promise(resolve => setTimeout(resolve, 1000));
   5   │ }
   6
   7   │ console.time("fetch(https://example.com)");
   8await fetch("https://example.com");
   9   │ console.timeEnd("fetch(https://example.com)");

改進了 Remix 支援

我們修復了一個錯誤,即覆蓋像 RequestResponse 這樣的全局變數可能會導致 node:http 出現問題。在內部,Bun 的 node:http 實現使用 Bun.serve(),它期望使用者返回原生的 Response 物件。

Remix 和其他框架全局 polyfill RequestResponse,而 Bun 的 node:http 之前使用的是全局 RequestResponse 物件。我們通過始終在 node:http 中使用原生的 RequestResponse 物件來修復此問題。

我們還在 Bun 的 node-fetchundici 的 polyfill 中修復了類似的問題。

如果您遇到以下錯誤,那麼現在已修復

// error: Expected a Response object, but received 'Response {
//   [Symbol(Body internals)]: {
//     body: ReadableStream {
// ...

我們還為 Remix 新增了一個整合測試,以便我們將來可以繼續確保相容性。

bun install 在 HTTP 500 錯誤時重試

在此版本中,bun install 現在將在網路錯誤且 HTTP 狀態碼 > 499 時重試。

我們已經看到來自使用者和我們自己的 CI 的報告,指出官方 npm 註冊表返回 HTTP 500 錯誤,這會導致 bun install 失敗。現在,bun install 將在這些錯誤時重試,這應該有助於補償 npm 註冊表的臨時問題(減少虛假的 CI 失敗)。

在 Linux 上縮小 4 MB

在此版本中,我們將 Bun 的 Linux x64 二進制檔案大小縮小了 4 MB,這使其比官方 Node.js v22.5.1 可執行檔小 23%。

二進制檔案大小版本
92 MBBun v1.1.21
96 MBBun v1.1.20
97 MBBun v1.1.19
97 MBBun v1.1.18
114 MBNode.js v22.5.1

在 Linux 上速度提升 1 毫秒

我們還將 Bun 在 Linux 上的啟動時間縮短了 1 毫秒。

hyperfine "bun --reivison" "bun-1.1.20 --revision"

Benchmark 1: bun --revision
  Time (mean ± σ):       1.4 ms ±   0.2 ms    [User: 0.9 ms, System: 0.4 ms]
  Range (min … max):     1.2 ms …   2.6 ms    1143 runs

Benchmark 2: bun-1.1.20 --revision
  Time (mean ± σ):       2.3 ms ±   0.2 ms    [User: 1.1 ms, System: 1.1 ms]
  Range (min … max):     2.2 ms …   5.0 ms    1099 runs

這些大小和啟動時間的改進來自連結器標誌、編譯器標誌、連結時間優化和一些小的程式碼更改的組合。

使用 bun build --packages=external 捆綁套件時排除依賴項

您現在可以控制套件依賴項是否包含在您的捆綁包中。如果導入不是以 .../ 開頭,則它被視為套件。

命令列介面
bun build ./index.ts --packages external

您也可以在 JavaScript API 中使用它

JavaScript
await Bun.build({
  entrypoints: ["./index.ts"],
  packages: "external",
});

這在捆綁函式庫時非常有用。它使您可以減少使用者必須下載的檔案數量,同時繼續支援對等或外部依賴項。

感謝 @zpix1 實作此功能!

改進了 bun build 中程式碼分割的雜湊

以前,Bun 會在程式碼分割期間為檔案生成「隔離」雜湊,以便可以並行完成。但是,這意味著如果導入的檔案發生更改,則擁有導入的檔案的雜湊將不會更改。

entry.ts
const other = await import('./second');
second.ts
export const second = 1;

此問題已修復,並且通過切換到 base32 編碼,雜湊也變得更短,類似於 esbuild 的做法。

感謝 @paperclover 參與此項工作!

已修復:當檔案為 .mts 時,導入解析為 .mjs

我們修復了一個錯誤,即 .mjs 導入不會解析為 .mts 檔案。

bar.mts
import "./foo.mjs";
foo.mts
export const foo = 1;

感謝 @190n,此問題現已修復!

已修復:在 Windows 上使用 Bun.write 時崩潰

我們修復了 Windows 上 Bun.write() 中的一個錯誤,其中 Bun 在為不存在的檔案建立目錄後可能會崩潰。

import { write, file } from "bun";

const txt = file("/does/not/exist/example.txt");
await write(txt, "hello world");

已修復:setSystemTime 無法與數字一起使用

存在一個錯誤,即從 bun:test 模組使用 setSystemTime 無法與數字一起使用,而是需要 Date 物件。

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

test("setSystemTime", () => {
  const future = Date.now() + 1000;
  setSystemTime(future);

  expect(Date.now()).toBe(future);
  // Before: <test failed>
  // After: <test passed>
});

感謝 @cirospaciari 修復此錯誤!

已修復:合併巢狀 TypeScript 命名空間時崩潰

Bun 中存在一個錯誤,即 TypeScript 命名空間可能會導致 Bun 崩潰。這是 Bun 轉譯器中的一個邊緣案例,其中它沒有正確處理函數與類別或命名空間合併時的情況。

namespace X {
  export function Y(): void {}
  export namespace Y {
    export const Z = 1;
  }
}

感謝 @paperclover 修復此錯誤!

感謝 @190n,此問題現已修復!

已修復:伺服器關閉後中止請求時崩潰

我們修復了 Bun.serve() 中的一個罕見崩潰,如果伺服器在請求完成發送正文之前關閉後手動中止 Request,則可能會發生此崩潰。

感謝 @cirospaciari 修復此錯誤!

已修復:CryptoHasher.update() 中使用空值時崩潰

我們修復了 CryptoHasher.update() 中的一個錯誤,其中空值會導致崩潰。

import { CryptoHasher } from "bun";

const hasher = new CryptoHasher("sha1");
hasher.update(); // <crash>

感謝 @cirospaciari 修復此錯誤!

已修復:列印錯誤堆疊時崩潰

在 Bun 中列印堆疊追蹤時可能會發生崩潰。這是由於未正確檢查堆疊幀是來自 JavaScript 函數還是來自原生函數造成的。

感謝 @nektro 修復此錯誤!

已修復:將 expect.any()expect.toThrow() 一起使用

expect.toThrow() 的預期值現在可以使用非對稱匹配器 expect.any()。以下程式碼將按預期工作

test("toThrow asymmetric matcher", () => {
  expect(() => {
    throw new Error("error!");
  }).toThrow(expect.any(Error)); // passes
});

感謝 @ippsav

已修復 bun test 中 test.each 的傳回值導致崩潰的問題

bun test 中存在一個錯誤,其中 test.each 的傳回值可能使用空指針,如果該值在 JavaScript 中使用,則會導致崩潰。現在,test.each 將返回 undefined

console.log(test.each([1, 2])("test.each %d", () => {})); // Before: <crash>, After: undefined

感謝 @dylan-conway

已修復:Windows 上的程式碼覆蓋率現在排除 node_modules

我們修復了 bun test --coverage 中的一個錯誤,其中程式碼覆蓋率報告將包含來自 Windows 上 node_modules 的檔案。這是由於僅檢查 / 作為路徑分隔符引起的,而在 Windows 上,路徑分隔符可能是 \/

感謝 @dariushalipour 修復此錯誤!

Node.js 相容性改進

已修復:node:http 請求 'data' 回調中的靜默錯誤

已修復導致 node:http 客戶端請求的 data 回調中錯誤靜默的錯誤。

Previously, the following code would not error

const { request } = require("node:http");
const req = request("http://www.google.com", (res) => {
  res.on("data", (chunk) => {
    throw new Error("oopsie");
  });
}).end();

Now, it correctly throws an error

1 | const { request } = require("node:http");
2 | const req = request("http://www.google.com", (res) => {
3 |   res.on("data", (chunk) => {
4 |     throw new Error("oopsie");
                  ^
error: oopsie
      at silent.js:4:15
      at emit (node:events:180:48)
      at addChunk (node:stream:2029:22)
      at readableAddChunk (node:stream:1983:30)
      at node:http:136:44

Bun v1.1.21 (macOS arm64)

已修復:Readable.fromWeb 在使用 fetch() 串流時過早停止

我們修復了一個錯誤,即當從 fetch() 傳遞 ReadableStream 時,Readable.fromWeb 在某些情況下會過早停止讀取,如果串流尚未完成下載。

import { Readable } from "node:stream";

const res = await fetch("https://bun.dev.org.tw");

const stream = Readable.fromWeb(res.body);

stream.on("data", (chunk) => {
  console.log("Received", chunk.length);
});

新的

❯ bun chunk.js
Received 16384
[... more chunks ...]
Received 3883

先前

❯ bun-1.1.20 chunk.js # Before

此錯誤專門影響 fetch() 的 ReadableStream。

已修復:允許 path.basename() 中使用 undefined 擴展名

我們修復了 path.basename() 中的一個相容性錯誤,其中它不允許將 undefined 作為擴展名。

import path from "path";

path.basename("foo", undefined);
path.posix.basename("bar", undefined);
path.win32.basename("baz", undefined);

如果您遇到以下錯誤,那麼現在已修復

> 2 | path.posix.basename("bar", undefined);
                          ^
TypeError: "ext" property must be of type string, got undefined
 code: "ERR_INVALID_ARG_TYPE"

感謝 @190n

已修復:在連線 socket 之前,node:dgram 中的 Unref

我們修復了 node:dgram 中的一個錯誤,如果在連線 socket 之前完成,則它不會正確地 unref UDP socket。

import dgram from "node:dgram";

const socket = dgram.createSocket("udp4");
socket.unref(); // <would not unref>

感謝 @190n 修復此錯誤!

已修復:napi_threadsafe_function 在完成後會保持進程存活

我們修復了 Node-API 中的一個回歸問題,即在 napi_threadsafe_function 完成後,它將繼續保持進程存活。感謝 @dylan-conway 修復此問題!

...
napi_threadsafe_function tsfn;
...
napi_release_threadsafe_function(tsfn, napi_tsfn_release);
// `tsfn` will allow the process to exit if all threads have released it.
...

已修復:process.exitCode 在呼叫 process.exit() 之前應為 undefined

在 Node.js 中,process.exitCode 在以下任一情況發生之前都是 undefined

  • 手動設定 process.exitCode
  • 呼叫 process.exit()

我們修復了 Bun 中的一個錯誤,其中 process.exitCode 會在呼叫 process.exit() 之前設定為 0

console.log(process.exitCode); // Before: 0, After: undefined

感謝 @nektro 修復此錯誤!

已修復:node:zlib brotli 解壓縮時崩潰

當長時間同步解壓縮由許多區塊組成的 brotli 串流時,可能會發生崩潰。此問題已修復。崩潰是由於在解壓縮發生時未正確保持來自 JavaScript 的值處於活動狀態而引起的。

感謝 @dylan-conway 修復此錯誤!

捆綁器修復

稍小的捆綁包

已修復:使用外部屬性存取進行捆綁時,bun build 中崩潰

我們修復了 bun build 中的一個錯誤,即當模組在導入的外部模組上使用屬性存取時可能會發生崩潰。

感謝 @paperclover,此問題已修復!

已修復:在使用 bun build 時,require() 中的尾部斜線

我們修復了一個錯誤,即在使用 bun build 時,帶有尾部斜線的 require() 呼叫將無法工作。

為了與 Node.js 匹配,如果您嘗試 require process/,它應該解析為 node_modules/process/...。以前,Bun 會感到困惑並解析為內建的 node:process 模組。

var process2 = require("process/");
// Error: Cannot find module 'process/'

感謝 @paperclover,此問題現已修復!

已修復:使用繩索字串導入時崩潰

Bun 中存在一個錯誤,即 import()with 選項中的非文字值會導致 Bun 崩潰。這是因為 Bun 的轉譯器未正確訪問 with 選項。

import("./foo", { with: "text" }); // would work
import("./foo", { with: "te" + "xt" }); // would crash

感謝 @paperclover,此問題現已修復!

已修復:類別提升錯誤

A bug causing the following error has been fixed

TypeError: The superclass is not a constructor.

當類別宣告具有副作用時,它不能被提升。我們修復了一個錯誤,即 Bun 的捆綁器會提升具有副作用的類別宣告。

entry.js
other.js
entry.js
async function hi() {
  const { default: MyInherited } = await import('./other.js');
  const myInstance = new MyInherited();
  console.log(myInstance.greet())
}

hi();
other.js
const MyReassignedSuper = class MySuper {
  greet() {
    return 'Hello, world!';
  }
};

class MyInherited extends MyReassignedSuper {};

export default MyInherited;

This produces the correct output now

diff.js
new.js
old.js
diff.js
-   class MyInherited extends MyReassignedSuper {
-   }
-   var MyReassignedSuper, other_default;
-     MyReassignedSuper = class MySuper {
-       greet() {
-         return "Hello, world!";
-       }
+ var MyReassignedSuper = class MySuper {
+   greet() {
+     return "Hello, world!";
+   }
+ }, MyInherited, other_default;
+   MyInherited = class MyInherited extends MyReassignedSuper {
+   };
+   other_default = MyInherited;
// ...
new.js
var __defProp = Object.defineProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);

// other.js
var exports_other = {};
__export(exports_other, {
  default: () => other_default
});
var MyReassignedSuper = class MySuper {
  greet() {
    return "Hello, world!";
  }
}, MyInherited, other_default;
var init_other = __esm(() => {
  MyInherited = class MyInherited extends MyReassignedSuper {
  };
  other_default = MyInherited;
});

// entry.js
async function hi() {
  const { default: MyInherited2 } = await Promise.resolve().then(() => (init_other(), exports_other));
  const myInstance = new MyInherited2;
  console.log(myInstance.greet());
}
hi();
old.js
var __defProp = Object.defineProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);

// other.js
var exports_other = {};
__export(exports_other, {
  default: () => other_default
});

class MyInherited extends MyReassignedSuper {
}
var MyReassignedSuper, other_default;
var init_other = __esm(() => {
  MyReassignedSuper = class MySuper {
    greet() {
      return "Hello, world!";
    }
  };
  other_default = MyInherited;
});

// entry.js
async function hi() {
  const { default: MyInherited2 } = await Promise.resolve().then(() => (init_other(), exports_other));
  const myInstance = new MyInherited2;
  console.log(myInstance.greet());
}
hi();

內部:macOS 和 Windows 上的 LLVM 18

我們已將 macOS 和 Windows 上的 LLVM 從 16 升級到 18。這使我們可以在 macOS 上添加更多偵錯斷言,這有助於更早地捕獲某些錯誤。

感謝 14 位貢獻者!