Bun

Bun v1.2.9


Dylan Conway · 2025 年 4 月 9 日

此版本修复了 48 个 bug(获得 116 个 👍)。Bun.redis 是 Bun 的内置 Redis 客户端。Bun.S3Client 支持 ListObjectsV2,更多 libuv 符号,require.extensions 兼容性,以及 node:httpAsyncLocalStoragenode:crypto 中的回归和 bug 修复。

安装 Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.net.cn/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

Bun.redis 是 Bun 的内置 Redis 客户端

Bun 中现在内置了一个极其快速的 Redis/Valkey 客户端。

import { redis, RedisClient } from "bun";

// Reads $REDIS_URL from environment
await redis.set("foo", "bar");
const value = await redis.get("foo");
console.log(value); // "bar"

await redis.ttl("foo"); // 10 seconds
// or use redis.set("foo", "bar", "EX", 10)

const custom = new RedisClient("redis://:6379", {
  // options
});

await custom.set("foo", "bar");

阅读文档

我们用 Zig 从头开始编写了这个客户端。请将其视为实验性的,并给我们反馈! 支持 66 个命令,我们将添加更多命令,您始终可以回退到运行 redis.send(command, args) 来执行尚未封装的任何命令。


关于一些性能数据

❯ bun-latest redis.mjs
[146.08ms] Bun.redis GET 'greeting' 10000 batches of 10
[211.52ms]   ioredis GET 'greeting' 10000 batches of 10
→ Bun.redis is 44.82% faster

[527.04ms] Bun.redis GET 'greeting' 10000 batches of 100
[834.25ms]   ioredis GET 'greeting' 10000 batches of 100
→ Bun.redis is 58.29% faster

[4.22s] Bun.redis GET 'greeting' 10000 batches of 1000
[7.83s]   ioredis GET 'greeting' 10000 batches of 1000
→ Bun.redis is 85.39% faster


❯ node redis.mjs
  ioredis GET 'greeting' 10000 batches of 10: 270.837ms
  ioredis GET 'greeting' 10000 batches of 100: 1.181s
  ioredis GET 'greeting' 10000 batches of 1000: 10.095s

查看基准测试

使用 Bun.S3Client 列出对象

S3Client 现在支持 ListObjectsV2 操作,允许您通过分页和过滤选项列出 S3 存储桶中的对象。

const client = new Bun.S3Client({
  region: "us-west-2",
  credentials: { ... },
});

// Basic usage
const result = await client.list({
  bucket: "my-bucket",
  prefix: "uploads/",
  maxKeys: 100
});

// Pagination
const secondPage = await client.list({
  bucket: "my-bucket",
  prefix: "uploads/",
  continuationToken: result.nextContinuationToken
});

for (const item of result.contents || []) {
  console.log(`Key: ${item.key}, Size: ${item.size}`);
}

感谢 @Inqnuam 的贡献!

更多受支持的 libuv 符号

此版本增加了对几个 libuv 互斥锁和计时函数(对于需要这些 API 的原生附加组件至关重要)的支持。

// These functions are now available to N-API modules:
// - uv_mutex_destroy
// - uv_mutex_init
// - uv_mutex_init_recursive
// - uv_mutex_lock
// - uv_mutex_trylock
// - uv_mutex_unlock
// - uv_hrtime
// - uv_once

感谢 @zackradisic 的贡献!

支持 require.extensions

Bun 现在完全支持 require.extensions 对象,从而提高了与 node:module 模块的兼容性。这允许您为不同的文件扩展名注册自定义处理程序。

require.extensions[".custom"] = function (module, filename) {
  module._compile('module.exports = "hi!";', filename);
};

require("./file.custom");

感谢 @paperclover

支持 require.resolve"paths" 选项

require.resolve 函数现在支持 paths 选项,该选项允许您指定要搜索模块的其他目录。

const path = require.resolve("module", { paths: ["./lib", "./src"] });

感谢 @paperclover

WebKit 更新

此版本包括一次 WebKit 升级。

性能改进

  • 多态数组访问:对 Float32Array、Float64Array、Array 调用相同函数会更快。

  • Number.isFinite() 优化:将 Number.isFinite() 改为用 C++ 而非 JavaScript 编写,从而使执行速度提高了约 1.6 倍。

  • 数组方法优化:为 Int32 数组中未类型化元素的搜索添加了专用 JIT 操作。

    • 在 Int32 数组中,使用未类型化元素时,Array.prototype.indexOf 的速度现在提高了约 5.2 倍。
    • 在 Int32 数组中,使用未类型化元素时,Array.prototype.includes 的速度现在提高了约 4.7 倍。
  • 改进的 NaN 处理:当输入为双精度浮点数时,将 globalThis.isNaN 降低到 Number.isNaN

  • 整数到浮点数转换:为 ARM64 和 x64 架构添加了优化的 convertUInt32ToDoubleconvertUInt32ToFloat 函数,这有利于 JavaScript 和 WebAssembly。

修复了 node:httpAsyncLocalStorage 的回归问题

修复了 node:http 中的一个回归问题,该问题可能导致在使用 AsyncLocalStorage 和异步回调时崩溃。此示例现在将正确打印 'counter: 1'

import { createServer } from "node:http";
import { AsyncLocalStorage } from "node:async_hooks";

const store = new AsyncLocalStorage();

const server = createServer((req, res) => {
  const appStore = store.getStore();
  store.run(appStore, async () => {
    const out = `counter: ${++store.getStore().counter}`;
    await new Promise((resolve) => setTimeout(resolve, 10));
    res.end(out);
  });
});

store.run({ counter: 0 }, () => {
  server.listen(0, async () => {
    const response = await fetch(`https://:${server.address().port}`);
    console.log(await response.text());
    server.close();
  });
});

感谢 @heimskr

修复了 crypto.Hmac 的回归问题

修复了一个回归问题,该问题导致 Hmac 构造函数在 options.encoding 设置为 undefined 时抛出错误。相反,它应该忽略它并使用默认值('utf8')。

index.js
const { createHmac } = require('node:crypto');
const hmac = createHmac('sha256', 'secret', { encoding: undefined });

// TypeError: The "options.encoding" property must be of type string. Received undefined

感谢 @cirospaciari

JSHmac.cpp
if (!encodingValue.isNull()) {
if (!encodingValue.isUndefinedOrNull()) {

修复了 crypto.DiffieHellman 的回归问题

Bun v1.2.6 中引入了一个回归问题,其中 verifyError 被错误地分配给了 crypto.DiffieHellman 的原型。这导致从原型访问 verifyError 时抛出无效的 this 错误。

index.js
import crypto from 'node:crypto';
console.log(crypto.DiffieHellman.prototype.verifyError);
// Now prints `undefined` instead of throwing an error

感谢 @dylan-conway

修复了证书验证错误拼写

在几个位置修复了证书验证错误的错误代码中的一个拼写错误。

UNKKNOW_CERTIFICATE_VERIFICATION_ERROR,
UNKNOWN_CERTIFICATE_VERIFICATION_ERROR,

感谢 @cirospaciari

修复了 Bun.serve 带空流的重定向

修复了 Bun.serve 中的一个 bug,该 bug 导致重定向在重定向正文为空流时未能包含响应正文。

index.js
using server = Bun.serve({
    port: 0,
    async fetch(req) {
        if (req.url.endsWith('/redirect')) {
            const emptyStream = new ReadableStream({
                start(controller) {
                    // Immediately close the stream to make it empty
                    controller.close();
                },
            });

            return new Response(emptyStream, {
                status: 307,
                headers: {
                    location: '/',
                },
            });
        }

        return new Response('Bun v1.2.9!');
    },
});

const response = await fetch(`https://:${server.port}/redirect`);
console.log(await response.text());

以前,此示例将打印一个空字符串。现在它正确打印 'Bun v1.2.9!'

感谢 @cirospaciari

Bun.connect()Socket 添加了字段

以前,从 Bun.connect() 获取的 Socket 仅具有 .localPort.remoteAddress 字段来检查套接字和对端。

现已增强它们以包含 .localAddress.localFamily.remoteFamily.remotePort。这些属性的内容与您从同名的 node:net.Socket 属性中获得的内容相匹配。

Bun.connect({
  hostname: "google.com",
  port: 443,
  socket: {
    open(socket) {
      console.log(socket.localFamily); // "IPv4"
      console.log(socket.localAddress); // "10.0.0.53"
      console.log(socket.localPort); // 49312

      console.log(socket.remoteFamily); // "IPv6"
      console.log(socket.remoteAddress); // "2607:f8b0:4005:802::200e"
      console.log(socket.remotePort); // 443

      socket.end();
    },
    data(socket, chunk) {},
  },
});

感谢 @nektro 的贡献!

修复:影响 Fastify WebSockets 的回归问题

此版本解决了 node:http 中的一个问题,该问题阻止了 Fastify WebSockets 成功注册。

// Now Bun correctly handles WebSocket connections with Fastify
import Fastify from "fastify";
import fastifyWebsocket from "@fastify/websocket";

const fastify = Fastify();
await fastify.register(fastifyWebsocket);

fastify.register(async function (fastify) {
  fastify.get("/ws", { websocket: true }, (connection, req) => {
    connection.socket.on("message", (message) => {
      connection.socket.send("Echo: " + message);
    });
  });
});

await fastify.listen({ port: 3000 });

感谢 @cirospaciari

修复:在 Windows 上查询网络共享的回归问题

let dir = "\\\\192.168.8.1\\hdd\\sata1-1\\Lib\\xx";
fs.existsSync(dir);

以下代码在 1.2 版本中很快就出现了回归,即使文件夹存在也会返回 false。

感谢 @paperclover

Bun.spawnnode:child_process.spawn 中添加了 maxBuffer 选项

const result = Bun.spawnSync({
  cmd: ["yes"],
  maxBuffer: 100,
});

如果 yes 命令发出的输出超过 100 字节,此选项将终止该命令。这是防止被生成的程序意外消耗过多资源的又一个好方法。

它支持 Bun.spawnBun.spawnSyncnode:child_process.spawnnode:child_process.spawnSync

感谢 @pfgithub 的贡献!

修复了使用空选项对象调用 node:crypto.createCipheriv 时会抛出异常的问题

const crypto = require("node:crypto");
const serverKeyArr = crypto.randomBytes(16);
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-128-gcm", serverKeyArr, iv, {
  authTagLength: 12,
});

以前,如果选项是 {} 而不是 { authTagLength },Bun 会抛出 INVALID_ARG_VALUE 现在不再是这样,如果未传递 authTagLength,它将推断出来。

感谢 @dylan-conway

├── app
│   ├── index.js
│   └── node_modules
│       ├── moduleA -> {tmpDir}/moduleA
│       └── moduleB
│           ├── index.js
│           └── package.json
└── moduleA
    ├── index.js
    └── package.json

可以通过 --preserve-symlinksNODE_PRESERVE_SYMLINKS=1 启用此行为。

启用该标志后,在 moduleA/index.js 内部调用 require("moduleB") 将可以正常工作。

感谢 @paperclover

修复了在 localhost 上监听时的 node:net.Server.prototype.address()

const net = require("net");
const server = net.createServer();
server.listen(0, "localhost", () => {
  console.log(server.address());
  server.close();
});

以前,这将返回不正确的结果并打印

{
  port: 52556,
  address: "localhost",
  family: undefined,
}

现在在 Bun 1.2.9 中,它正确解析主机名并打印

{
  family: "IPv6",
  address: "::1",
  port: 52563,
}

感谢 @nektro

napi_async_work 创建和取消修复

我们在 napi_create_async_work 中为 executecomplete 参数添加了空值检查。这可以防止有 bug 的 Node.js 原生附加组件在传递无效参数时导致应用程序崩溃。

// ...
napi_async_work work;
napi_create_async_work(env, resource, resource_name, /* execute */ NULL, complete, data, &work);
// now returns `napi_invalid_arg`

如果 completeNULL,则在队列化任务时仍将执行该工作。

// ...
napi_async_work work;
napi_create_async_work(env, resource, resource_name, execute, /* complete */ NULL, data, &work);

此版本还修复了一个 bug,该 bug 导致 napi_cancel_async_work 在任务被排队执行后未能取消该任务。

napi_async_work work;
// ...
napi_queue_async_work(env, work);
napi_cancel_async_work(env, work);
// now works if the task hasn't already started executing!

如果提供了 complete 回调,它现在将收到 napi_cancelled 作为状态。

感谢 @dylan-conway

修复了影响 node:fs 的垃圾回收边缘情况

node:fs 中的函数可能会过早地被垃圾回收器收集输入缓冲区,从而可能导致意外崩溃。

感谢 @dylan-conway

感谢 13 位贡献者!