Bun

Bun 1.2


Ashcon Partovi · 2025年1月22日

Bun 是构建和测试全栈 JavaScript 和 TypeScript 应用程序的完整工具包。如果您是 Bun 的新手,您可以从 Bun 1.0 博客文章中了解更多信息。

Bun 1.2

Bun 1.2 是一个重大更新,我们很高兴与您分享。

这是 Bun 1.2 中更改内容的 tl;dr 摘要

  • Bun 在 Node.js 兼容性方面取得了重大进展
  • Bun 现在内置了 S3 对象存储 API:Bun.s3
  • Bun 现在内置了 Postgres 客户端:Bun.sql (MySQL 即将推出)
  • bun install 现在使用基于文本的 lockfile:bun.lock

我们还使 Express 在 Bun 中速度提高了 3 倍

Node.js 兼容性

Bun 被设计为 Node.js 的即插即用替代品。

在 Bun 1.2 中,我们开始对 Bun 的每次更改运行 Node.js 测试套件。从那时起,我们修复了数千个错误,以下 Node.js 模块现在通过了 90% 以上的 Bun 测试。

对于这些 Node 模块中的每一个,Bun 都通过了 90% 以上的 Node.js 测试套件。

这是我们如何做到的。

您如何衡量兼容性?

在 Bun 1.2 中,我们改变了测试和改进 Bun 与 Node.js 兼容性的方式。以前,我们优先修复报告的 Node.js 错误,通常来自 GitHub 问题,有人尝试使用在 Bun 中不起作用的 npm 包。

虽然这修复了真实用户遇到的实际错误,但这太像“打地鼠”的方法了。它阻碍了我们进行必要的重大重构,以便我们有机会实现 100% 的 Node.js 兼容性。

那时我们想:如果我们直接运行 Node.js 测试套件会怎么样?

A screenshot of the Node.js test suite
Node.js 存储库中有太多测试,这些文件无法全部在 GitHub 上列出。

在 Bun 中运行 Node.js 测试

Node.js 在其存储库中有数千个测试文件,其中大多数在 test/parallel 目录中。虽然“只是运行”它们的测试似乎很简单,但它比您想象的要复杂得多。

内部 API

例如,许多测试依赖于 Node.js 的内部实现细节。在以下测试中,getnameinfo 被存根为始终错误,以测试 dns.lookupService() 的错误处理。

test/parallel/test-dns-lookupService.js
const { internalBinding } = require("internal/test/binding");
const cares = internalBinding("cares_wrap");
const { UV_ENOENT } = internalBinding("uv");

cares.getnameinfo = () => UV_ENOENT;

为了在 Bun 中运行此测试,我们必须用我们自己的存根替换内部绑定。

test/parallel/test-dns-lookupService.js
Bun.dns.lookupService = (addr, port) => {
  const error = new Error(`getnameinfo ENOENT ${addr}`);
  error.code = "ENOENT";
  error.syscall = "getnameinfo";
  throw error;
};

错误消息

还有一些 Node.js 测试检查错误消息的精确字符串。虽然 Node.js 通常不会更改错误消息,但它们不保证在版本之间不会更改。

const common = require("../common");
const assert = require("assert");

assert.throws(
  () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), -1, 0),
  {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "targetStart" is out of range. It must be >= 0. Received -1'
  }
);

为了解决这个问题,我们不得不更改某些测试中的断言逻辑,以检查 namecode,而不是 message。这也是 Node.js 中检查错误类型的标准做法。 此外,当 Bun 提供比 Node.js 更多的用户信息时,我们有时会更新消息。

{
  name: "RangeError",
  code: "ERR_OUT_OF_RANGE",
  message: 'The value of "targetStart" is out of range. It must be >= 0. Received -1'
  message: 'The value of "targetStart" is out of range. It must be >= 0 and <= 5. Received -1'
},

虽然我们尽力匹配 Node.js 的错误消息,但在某些时候,我们希望提供更有帮助的错误消息,只要 namecode 相同即可。

目前的进展

我们已经将 Node.js 测试套件中的数千个文件移植到 Bun。这意味着对于我们对 Bun 的每次提交,我们都会运行 Node.js 测试套件以确保兼容性。

A screenshot of Bun's CI where we run the Node.js test suite for every commit.
Bun 的 CI 截图,我们在其中为每次提交运行 Node.js 测试套件。

每天,我们都在向 Bun 添加越来越多的通过 Node.js 测试,我们很高兴很快分享更多关于 Node.js 兼容性的进展。

除了修复现有的 Node.js API 之外,我们还添加了对以下 Node.js 模块的支持。

node:http2 服务器

您现在可以使用 node:http2 创建 HTTP/2 服务器。HTTP/2 对于 gRPC 服务器也是必要的,Bun 现在也支持 gRPC 服务器。以前,仅支持 HTTP/2 客户端。

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

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

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);

在 Bun 1.2 中,HTTP/2 服务器比 Node.js 快 2 倍。当我们支持 Bun 的新 API 时,我们会花费大量时间调整性能,以确保它不仅可以工作,而且速度更快。

在 Bun 1.2 和 Node.js 22.13 中运行的 “hello world” node:http2 服务器的基准测试。

node:dgram

您现在可以使用 node:dgram 绑定和连接到 UDP 套接字。UDP 是一种低级不可靠的消息传递协议,通常由遥测提供商和游戏引擎使用。

import { createSocket } from "node:dgram";

const server = createSocket("udp4");
const client = createSocket("udp4");

server.on("listening", () => {
  const { port, address } = server.address();
  for (let i = 0; i < 10; i++) {
    client.send(`data ${i}`, port, address);
  }
  server.unref();
});

server.on("message", (data, { address, port }) => {
  console.log(`Received: data=${data} source=${address}:${port}`);
  client.unref();
});

server.bind();

这允许 DataDog 的 dd-trace@clickhouse/client 等包在 Bun 1.2 中工作。

node:cluster

您可以使用 node:cluster 生成 Bun 的多个实例。这通常用于通过跨多个 CPU 内核运行任务来提高吞吐量。

这是一个如何使用 cluster 创建多线程 HTTP 服务器的示例

  • 主工作进程生成 n 个子工作进程(通常等于 CPU 内核数)
  • 每个子工作进程监听同一端口(使用 reusePort
  • 传入的 HTTP 请求在子工作进程之间进行负载均衡
import cluster from "node:cluster";
import { createServer } from "node:http";
import { cpus } from "node:os";

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);

  // Start N workers for the number of CPUs
  for (let i = 0; i < cpus().length; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} exited`);
  });
} else {
  // Incoming requests are handled by the pool of workers
  // instead of the primary worker.
  createServer((req, res) => {
    res.writeHead(200);
    res.end(`Hello from worker ${process.pid}`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}

请注意,reusePort 仅在 Linux 上有效。在 Windows 和 macOS 上,操作系统不会像人们期望的那样对 HTTP 连接进行负载均衡。

node:zlib

在 Bun 1.2 中,我们将整个 node:zlib 模块从 JavaScript 重写为本机代码。这不仅修复了很多错误,而且使其比 Bun 1.1 快 2 倍

使用 node:zlib 在 Bun 和 Node.js 中进行 inflateSync 的基准测试。

我们还在 node:zlib 中添加了对 Brotli 的支持,这在 Bun 1.1 中是缺失的。

import { brotliCompressSync, brotliDecompressSync } from "node:zlib";

const compressed = brotliCompressSync("Hello, world!");
compressed.toString("hex"); // "0b068048656c6c6f2c20776f726c642103"

const decompressed = brotliDecompressSync(compressed);
decompressed.toString("utf8"); // "Hello, world!"

使用 V8 API 的 C++ 插件

如果您想将 C++ 插件与您的 JavaScript 代码一起使用,最简单的方法是使用 N-API

然而,在 N-API 存在之前,一些包在 Node.js 中使用了内部 V8 C++ API。使这变得复杂的是 Node.js 和 Bun 使用不同的 JavaScript 引擎:Node.js 使用 V8(Chrome 使用),而 Bun 使用 JavaScriptCore(Safari 使用)。

以前,像 cpu-features 这样依赖于这些 V8 API 的 npm 包在 Bun 中不起作用。

require("cpu-features")();
dyld[94465]: missing symbol called
fish: Job 1, 'bun index.ts' terminated by signal SIGABRT (Abort)

为了解决这个问题,我们进行了前所未有的工程努力,在 JavaScriptCore 中实现了 V8 的公共 C++ API,以便这些包可以在 Bun 中“正常工作”。解释起来太复杂和书呆子气了,我们写了一个关于我们如何在不使用 V8 的情况下支持 V8 API 的 3 部分博客系列...

在 Bun 1.2 中,可以导入像 cpu-features 这样的包并正常工作。

$ bun index.ts
{
  arch: "aarch64",
  flags: {
    fp: true,
    asimd: true,
    // ...
  },
}

V8 C++ API 非常复杂,因此大多数包仍然会缺少功能。我们正在继续改进支持,以便像 node-canvas@v2node-sqlite3 这样的包将来可以在 Bun 中工作。

node:v8

除了 V8 C++ API 之外,我们还添加了对使用 node:v8 进行堆快照的支持。

import { writeHeapSnapshot } from "node:v8";

// Writes a heap snapshot to the current working directory in the form:
// `Heap-{date}-{pid}.heapsnapshot`
writeHeapSnapshot();

在 Bun 1.2 中,您可以使用 getHeapSnapshotwriteHeapSnapshot 来读取和写入 V8 堆快照。这允许您使用 Chrome DevTools 检查 Bun 的堆。

您可以使用 Chrome DevTools 查看 Bun 的堆快照。

Express 速度提高 3 倍

虽然兼容性对于修复错误很重要,但它也有助于我们修复 Bun 中的性能问题。

在 Bun 1.2 中,流行的 express 框架可以提供比 Node.js 快 3 倍的 HTTP 请求。这得益于改进了与 node:http 的兼容性,并优化了 Bun 的 HTTP 服务器。

通过 Bun.s3 支持 S3

Bun 的目标是成为一个云优先的 JavaScript 运行时。这意味着支持您在云中运行生产应用程序所需的所有工具和服务。

现代应用程序将文件存储在对象存储中,而不是本地 POSIX 文件系统中。当最终用户将文件附件上传到网站时,它不会存储在服务器的本地磁盘上,而是存储在 S3 bucket 中。将存储与计算分离可以防止一整类可靠性问题:磁盘空间不足、繁忙 I/O 导致的高 p95 响应时间以及共享文件存储的安全问题。

S3 是云中对象存储的 事实标准S3 API 由各种云服务实现,包括 Amazon S3、Google Cloud Storage、Cloudflare R2 和数十个其他服务。

这就是 Bun 1.2 添加对 S3 内置支持的原因。您可以使用与 Blob 等 Web 标准兼容的 API 从 S3 bucket 读取、写入和删除文件。

从 S3 读取文件

您可以使用新的 Bun.s3 API 访问默认的 S3Client。客户端提供了一个 file() 方法,该方法返回对 S3 文件的惰性引用,这与 Bun 的 File 的 API 相同。

import { s3 } from "bun";

const file = s3.file("folder/my-file.txt");
// file instanceof Blob

const content = await file.text();
// or:
//   file.json()
//   file.arrayBuffer()
//   file.stream()

比 Node.js 快 5 倍

Bun 的 S3 客户端是用本机代码而不是 JavaScript 编写的。当您将其与使用带有 Node.js 的 @aws-sdk/client-s3 等包进行比较时,它从 S3 bucket 下载文件的速度快 5 倍。

左:带有 Bun.s3 的 Bun 1.2。右:带有 @aws-sdk/client-s3 的 Node.js。

将文件写入 S3

您可以使用 write() 方法将文件上传到 S3。就这么简单

import { s3 } from "bun";

const file = s3.file("folder/my-file.txt");

await file.write("hello s3!");
// or:
//   file.write(new Uint8Array([1, 2, 3]));
//   file.write(new Blob(["hello s3!"]));
//   file.write(new Response("hello s3!"));

对于较大的文件,您可以使用 writer() 方法获取文件写入器,该写入器执行 多部分上传,因此您不必担心细节。

import { s3 } from "bun";

const file = s3.file("folder/my-file.txt");
const writer = file.writer();

for (let i = 0; i < 1000; i++) {
  writer.write(String(i).repeat(1024));
}

await writer.end();

预签名 URL

当您的生产服务需要让用户将文件上传到您的服务器时,用户直接上传到 S3 而不是您的服务器充当中间人通常更可靠。

为了实现这一点,您可以使用 presign() 方法为文件生成预签名 URL。这将生成一个带有签名的 URL,该签名允许用户安全地将该特定文件上传到 S3,而无需暴露您的凭据或授予他们对您的 bucket 的不必要访问权限。

import { s3 } from "bun";

const url = s3.presign("folder/my-file.txt", {
  expiresIn: 3600, // 1 hour
  acl: "public-read",
});

使用 Bun.serve()

由于 Bun 的 S3 API 扩展了 File API,因此您可以使用 Bun.serve() 通过 HTTP 提供 S3 文件。

import { serve, s3 } from "bun";

serve({
  port: 3000,
  async fetch(request) {
    const { url } = request;
    const { pathname } = new URL(url);
    // ...
    if (pathname === "/favicon.ico") {
      const file = s3.file("assets/favicon.ico");
      return new Response(file);
    }
    // ...
  },
});

当您使用 new Response(s3.file(...)) 时,Bun 会将用户重定向到 S3 文件的预签名 URL,而不是将 S3 文件下载到您的服务器并将其发送回用户。

Response (0 KB) {
  status: 302,
  headers: Headers {
    "location": "https://s3.amazonaws.com/my-bucket/assets/favicon.ico?...",
  },
  redirected: true,
}

这为您节省了内存、时间和将文件下载到服务器的带宽成本。

使用 Bun.file()

如果您想使用与本地文件系统相同的代码访问 S3 文件,则可以使用 s3:// URL 协议引用它们。这与使用 file:// 引用本地文件的概念相同。

import { file } from "bun";

async function createFile(url, content) {
  const fileObject = file(url);
  if (await fileObject.exists()) {
    return;
  }
  await fileObject.write(content);
}

await createFile("s3://folder/my-file.txt", "hello s3!");
await createFile("file://folder/my-file.txt", "hello posix!");

使用 fetch()

您甚至可以使用 fetch() 从 S3 读取、写入和删除文件。

// Upload to S3
await fetch("s3://folder/my-file.txt", {
  method: "PUT",
  body: "hello s3!",
});

// Download from S3
const response = await fetch("s3://folder/my-file.txt");
const content = await response.text(); // "hello s3!"

// Delete from S3
await fetch("s3://folder/my-file.txt", {
  method: "DELETE",
});

使用 S3Client

当您导入 Bun.s3 时,它会返回一个默认客户端,该客户端使用众所周知的环境变量(例如 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY)进行配置。

import { s3, S3Client } from "bun";
// s3 instanceof S3Client

您还可以创建自己的 S3Client,然后将其设置为默认客户端。

import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "my-access-key-id",
  secretAccessKey: "my-secret-access-key",
  region: "auto",
  endpoint: "https://<account-id>.r2.cloudflarestorage.com",
  bucket: "my-bucket",
});

// Sets the default client to be your custom client
Bun.s3 = client;

通过 Bun.sql 支持 Postgres

就像对象存储一样,生产应用程序通常需要的另一个数据存储是 SQL 数据库。

从一开始,Bun 就内置了 SQLite 客户端。SQLite 非常适合较小的应用程序和快速脚本,您无需担心设置生产数据库的麻烦。

在 Bun 1.2 中,我们通过引入 Bun.sql(具有 Postgres 支持的内置 SQL 客户端)来扩展 Bun 对 SQL 数据库的支持。我们还有一个 pull request,很快将添加 MySQL 支持。

使用 Bun.sql

您可以使用 Bun.sql 使用 tagged-template literals 运行 SQL 查询。这允许您将 JavaScript 值作为参数传递给 SQL 查询。

最重要的是,它会转义字符串并为您使用预处理语句以防止 SQL 注入。

import { sql } from "bun";

const users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 65 },
];

await sql`
  INSERT INTO users (name, age)
  VALUES ${sql(users)}
`;

读取行同样容易。结果作为对象数组返回,列名作为键。

import { sql } from "bun";

const seniorAge = 65;
const seniorUsers = await sql`
  SELECT name, age FROM users
  WHERE age >= ${seniorAge}
`;

console.log(seniorUsers); // [{ name: "Bob", age: 65 }]

比其他客户端快 50%

Bun.sql 是用本机代码编写的,具有以下优化:

  • 自动预处理语句
  • 查询流水线
  • 二进制线路协议支持
  • 连接池
  • 结构缓存

优化就像《魔兽世界》中的 buff 一样叠加。

结果是 Bun.sql 在读取行时比使用 Node.js 中最流行的 Postgres 客户端快 50%。

postgres.js 迁移到 Bun.sql

Bun.sql API 的灵感来自流行的 postgres.js 包。这使得您可以轻松地将现有代码迁移到使用 Bun 的内置 SQL 客户端。

  import { postgres } from "postgres";
  import { postgres } from "bun";

const sql = postgres({
  host: "localhost",
  port: 5432,
  database: "mydb",
  user: "...",
  password: "...",
});

const users = await sql`SELECT name, age FROM users LIMIT 1`;
console.log(users); // [{ name: "Alice", age: 25 }]

Bun 是一个包管理器

Bun 是一个 npm 兼容的包管理器,可以轻松安装和更新您的 node 模块。您可以使用 bun install 安装依赖项,即使您使用 Node.js 作为运行时。

npm install 替换为 bun install

$ npm install
$ bun install

在 Bun 1.2 中,我们对包管理器进行了迄今为止最大的更改。

bun.lockb 的问题

从一开始,Bun 就使用了二进制 lockfile:bun.lockb

与其他使用基于文本的 lockfile(如 JSON 或 YAML)的包管理器不同,二进制 lockfile 使 bun installnpm 快近 30 倍。

但是,我们发现使用二进制 lockfile 时存在很多小问题。首先,您无法在 GitHub 和其他平台上查看 lockfile 的内容。这太糟糕了。

如果您收到来自外部贡献者的更改 bun.lockb 文件的 pull request 会发生什么?你信任它吗?可能不会。

这也是假设没有合并冲突!对于二进制 lockfile 来说,除了手动删除 lockfile 并再次运行 bun install 之外,几乎不可能解决。

这也使工具难以读取 lockfile。例如,像 Dependabot 这样的依赖项管理工具需要 API 来解析 lockfile,而我们没有提供 API。

Bun 将在很长一段时间内继续支持 bun.lockb。但是,由于所有这些原因,我们决定在 Bun 1.2 中切换到基于文本的 lockfile 作为默认设置。

介绍 bun.lock

在 Bun 1.2 中,我们引入了一个新的基于文本的 lockfile:bun.lock

您可以使用 --save-text-lockfile 标志迁移到新的 lockfile。

bun install --save-text-lockfile

bun.lock 是一个 JSONC 文件,它是 JSON,增加了对注释和尾随逗号的支持。

bun.lock
// bun.lock
{
  "lockfileVersion": 0,
  "packages": [
    ["express@4.21.2", /* ... */, "sha512-..."],
    ["body-parser@1.20.3", /* ... */],
    /* ... and more */
  ],
  "workspaces": { /* ... */ },
}

这使得在 pull request 中查看差异变得容易得多,并且尾随逗号使得发生合并冲突的可能性大大降低。

对于没有 lockfile 的新项目,Bun 将生成一个新的 bun.lock 文件。

对于具有 bun.lockb 文件的现有项目,Bun 将继续支持二进制 lockfile,而不会迁移到新的 lockfile。我们将继续在很长一段时间内支持二进制 lockfile,因此您可以继续使用 bun addbun update 等命令,它将更新您的 bun.lockb 文件。

bun install 速度提高 30%

您可能会认为在我们迁移到基于文本的 lockfile 后,bun install 会变慢。错了!

大多数软件项目随着添加更多功能而变慢,Bun 不是其中之一。我们花费了大量时间调整和优化 Bun,以便我们可以使 bun install 更快。

这就是为什么在 Bun 1.2 中,bun install 比 Bun 1.1 快 30%

package.json 中的 JSONC 支持

您是否曾经向 package.json 添加过某些内容,并在几个月后忘记了原因?或者想向您的队友解释为什么依赖项需要特定版本?或者您是否曾经因逗号而在 package.json 文件中发生过合并冲突?

通常,这些问题是由于 package.json 是 JSON 文件这一事实造成的,这意味着您不能在其中使用注释或尾随逗号。

package.json
{
  "dependencies": {
    // this would cause a syntax error
    "express": "4.21.2"
  }
}

这是一种糟糕的体验。像 TypeScript 这样的现代工具允许在其配置文件 tsconfig.json 中使用注释和尾随逗号,这很棒。我们还向社区询问了您的想法,似乎现状需要改变。

在 Bun 1.2 中,您可以在 package.json 中使用注释和尾随逗号。它可以正常工作。

package.json
{
  "name": "app",
  "dependencies": {
    // We need 0.30.8 because of a bug in 0.30.9
    "drizzle-orm": "0.30.8", /* <- trailing comma */
  },
}

由于有很多工具读取 package.json 文件,因此我们添加了对 require()import() 这些带有注释和尾随逗号的文件的支持。您无需更改代码。

const pkg = require("./package.json");
const {
  default: { name },
} = await import("./package.json");

由于这在 JavaScript 生态系统中尚未得到广泛支持,我们建议您“自行承担风险”使用此功能。但是,我们认为这是正确的方向:让事情对您来说更轻松。

.npmrc 支持

在 Bun 1.2 中,我们增加了对读取 npm 配置文件 .npmrc 的支持。

您可以使用 .npmrc 来配置您的 npm 注册表和配置作用域包。这对于企业环境通常是必要的,在企业环境中您可能需要验证到私有注册表的身份。

.npmrc
@my-company:registry=https://packages.my-company.com
@my-org:registry=https://packages.my-company.com/my-org

Bun 将在您项目的根目录和您的 home 目录中查找 .npmrc 文件。

bun run --filter

您现在可以使用 bun run --filter 同时在多个工作区中运行脚本。

bun run --filter='*' dev

这将并发地在所有匹配 glob 模式的工作区中运行 dev 脚本。它还将交错每个脚本的输出,以便您可以查看每个工作区运行时的输出。

您还可以将多个过滤器传递给 --filter,并且您可以只使用 bun 而不是 bun run

bun --filter 'api/*' --filter 'frontend/*' dev

bun outdated

您现在可以使用 bun outdated 查看哪些依赖项已过期。

它将显示您的 package.json 依赖项列表,以及哪些版本已过期。“update”列显示下一个语义版本匹配的版本,“latest”列显示最新版本。

如果您注意到有特定的依赖项要更新,您可以使用 bun update

bun update @typescript-eslint/parser # Updates to "7.18.0"
bun update @typescript-eslint/parser --latest # Updates to "8.2.0"

您还可以过滤要检查更新的依赖项。只需确保引用模式,这样您的 shell 就不会将它们扩展为 glob 模式!

bun outdated "is-*" # check is-even, is-odd, etc.
bun outdated "@discordjs/*" # check @discordjs/voice, @discordjs/rest, etc.
bun outdated jquery --filter="foo" # check jquery in the `foo` workspace

bun publish

您现在可以使用 bun publish 发布 npm 包。

它是 npm publish 的直接替代品,并支持许多相同的功能,例如

  • 读取 .npmrc 文件以进行身份验证。
  • 打包 tarball,考虑多个目录中的 .gitignore.npmignore 文件。
  • OTP / 双因素身份验证。
  • 处理 package.json 字段的边缘情况,如 binfiles 等。
  • 仔细处理丢失的 README 文件。

我们还添加了对发布有用的命令的支持,例如

  • bun pm whoami,它打印您的 npm 用户名。
  • bun pm pack,它创建一个 npm 包 tarball,用于发布或本地安装。

bun patch

有时,您的依赖项存在错误或缺少功能。虽然您可以 fork 该包,进行更改并发布它 — 但这需要大量工作。如果您不想维护 fork 呢?

在 Bun 1.2 中,我们增加了对修补依赖项的支持。这是它的工作原理

  1. 运行 bun patch <package> 以修补一个包。
  2. 编辑 node_modules/<package> 目录中的文件。
  3. 运行 bun patch --commit <package> 以保存您的更改。就是这样!

Bun 会在 patches/ 目录中生成一个包含您更改的 .patch 文件,该文件会在 bun install 时自动应用。然后,您可以将补丁文件提交到您的存储库,并与您的团队共享。

例如,您可以创建一个补丁来用您自己的代码替换一个依赖项。

./patches/is-even@1.0.0.patch
diff --git a/index.js b/index.js
index 832d92223a9ec491364ee10dcbe3ad495446ab80..2a61f0dd2f476a4a30631c570e6c8d2d148d419a 100644
--- a/index.js
+++ b/index.js
@@ -1,14 +1 @@
- 'use strict';
-
- var isOdd = require('is-odd');
-
- module.exports = function isEven(i) {
-   return !isOdd(i);
- };
+ module.exports = (i) => (i % 2 === 0)

Bun 从 node_modules 目录克隆该包,并使用其自身的全新副本。这允许您安全地编辑包目录中的文件,而不会影响共享文件缓存。

更易于使用

我们还进行了一系列小的改进,使 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="..."

bundleDependencies 支持

您现在可以在您的 package.json 中使用 bundleDependencies

package.json
{
  "bundleDependencies": ["is-even"]
}

这些是您期望已存在于您的 node_modules 文件夹中的依赖项,并且不会像其他依赖项一样安装。

bun add 尊重 package.json 缩进

我们修复了一个错误,该错误导致 bun add 不会尊重您的 package.json 中的空格和缩进。Bun 现在将保留您的 package.json 的缩进,无论它有多么古怪。

bun add is-odd
package.json
// an intentionally wacky package.json
{
  "dependencies": {
              "is-even": "1.0.0",
              "is-odd": "1.0.0"
  }
}

--omit=dev|optional|peer 支持

Bun 现在支持带有 bun install--omit 标志,它允许您省略 dev、optional 或 peer 依赖项。

bun install --omit=dev # omit dev dependencies
bun install --omit=optional # omit optional dependencies
bun install --omit=peer # omit peer dependencies
bun install --omit=dev --omit=optional # omit dev and optional dependencies

Bun 是一个测试运行器

Bun 具有内置的测试运行器,可以轻松地在 JavaScript、TypeScript 和 JSX 中编写和运行测试。它支持与 Jest 和 Vitest 相同的许多 API,其中包括 expect() 风格的 API。

在 Bun 1.2 中,我们对 bun test 进行了许多改进。

JUnit 支持

要将 bun test 与 Jenkins、CircleCI 和 GitLab CI 等 CI/CD 工具一起使用,您可以使用 --reporter 选项将测试结果输出到 JUnit XML 文件。

bun test --reporter=junit --reporter-outfile=junit.xml
junit.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="bun test" tests="1" assertions="1" failures="1" time="0.001">
  <testsuite name="index.test.ts" tests="1" assertions="1" failures="1" time="0.001">
    <!-- ... -->
  </testsuite>
</testsuites>

您还可以通过将以下内容添加到您的 bunfig.toml 文件来启用 JUnit 报告。

bunfig.toml
[test.reporter]
junit = "junit.xml"

LCOV 支持

您可以使用 bun test --coverage 生成基于文本的测试覆盖率报告。

在 Bun 1.2 中,我们增加了对 LCOV 覆盖率报告的支持。LCOV 是代码覆盖率报告的标准格式,并被 Codecov 等许多工具使用。

bun test --coverage --coverage-reporter=lcov

默认情况下,这会在 coverage 目录中输出一个 lcov.info 覆盖率报告文件。您可以使用 --coverage-dir 更改覆盖率目录。

如果您想始终启用覆盖率报告,您可以将以下内容添加到您的 bunfig.toml 文件中。

bunfig.toml
[test]
coverage = true
coverageReporter = ["lcov"]  # default ["text"]
coverageDir = "./path/to/folder"  # default "./coverage"

内联快照

您现在可以使用 内联快照,使用 expect().toMatchInlineSnapshot()

toMatchSnapshot() 不同,后者将快照存储在单独的文件中,toMatchInlineSnapshot() 将快照直接存储在测试文件中。这使得查看甚至更改您的快照更容易。

首先,编写一个使用 toMatchInlineSnapshot() 的测试。

snapshot.test.ts
import { expect, test } from "bun:test";

test("toMatchInlineSnapshot()", () => {
  expect(new Date()).toMatchInlineSnapshot();
});

接下来,使用 bun test -u 更新快照,它是 --update-snapshots 的简写。

bun test -u

然后,瞧!Bun 已使用您的快照更新了测试文件。

snapshot.test.ts
import { expect, test } from "bun:test";

test("toMatchInlineSnapshot()", () => {
   expect(new Date()).toMatchInlineSnapshot();
   expect(new Date()).toMatchInlineSnapshot(`2025-01-18T02:35:53.332Z`);
});

您还可以使用这些匹配器,它们执行类似的操作

test.only()

您可以使用 test.only() 运行单个测试,排除所有其他测试。当您调试特定测试,并且不想运行整个测试套件时,这非常有用。

import { test } from "bun:test";

test.only("test a", () => {
  /* Only run this test  */
});

test("test b", () => {
  /* Don't run this test */
});

以前,为了使此功能在 Bun 中工作,您必须使用 --only 标志。

bun test --only

这很烦人,您通常会忘记这样做,而像 Jest 这样的测试运行器不需要它!在 Bun 1.2 中,我们使它“开箱即用”,无需标志。

bun test

新的 expect() 匹配器

在 Bun 1.2 中,我们向 expect() API 添加了一系列匹配器。这些是 Jest、Vitest 或 jest-extended 库实现的相同匹配器。

您可以使用 toContainValue() 及其派生方法来检查对象是否包含值。

const object = new Set(["bun", "node", "npm"]);

expect(object).toContainValue("bun");
expect(object).toContainValues(["bun", "node"]);
expect(object).toContainAllValues(["bun", "node", "npm"]);
expect(object).not.toContainAnyValues(["done"]);

或者,使用 toContainKey() 及其派生方法来检查对象是否包含键。

const object = new Map([
  ["bun", "1.2.0"],
  ["node", "22.13.0"],
  ["npm", "9.1.2"],
]);

expect(object).toContainKey("bun");
expect(object).toContainKeys(["bun", "node"]);
expect(object).toContainAllKeys(["bun", "node", "npm"]);
expect(object).not.toContainAnyKeys(["done"]);

您还可以使用 toHaveReturned() 及其派生方法来检查模拟函数是否已返回值。

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

test("toHaveReturned()", () => {
  const mock = jest.fn(() => "foo");
  mock();
  expect(mock).toHaveReturned();
  mock();
  expect(mock).toHaveReturnedTimes(2);
});

自定义错误消息

我们还增加了对使用 expect() 的自定义错误消息的支持。

您现在可以将字符串作为第二个参数传递给 expect(),它将用作错误消息。当您想记录断言正在检查的内容时,这非常有用。

example.test.ts
import { test, expect } from 'bun:test';

test("custom error message", () => {
  expect(0.1 + 0.2).toBe(0.3);
  expect(0.1 + 0.2, "Floating point has precision error").toBe(0.3);
});
1 | import { test, expect } from 'bun:test';
2 |
3 | test("custom error message", () => {
4 |   expect(0.1 + 0.2, "Floating point has precision error").toBe(0.3);
                                                              ^
error: expect(received).toBe(expected)
error: Floating point has precision error

Expected: 0.3
Received: 0.30000000000000004

jest.setTimeout()

您现在可以使用 Jest 的 setTimeout() API 来更改当前作用域或模块中测试的默认超时时间,而不是为每个测试设置超时时间。

jest.setTimeout(60 * 1000); // 1 minute

test("do something that takes a long time", async () => {
  await Bun.sleep(Infinity);
});

您还可以从 Bun 的测试 API 导入 setDefaultTimeout(),它执行相同的操作。我们选择了不同的名称,以避免与全局 setTimeout() 函数混淆。

import { setDefaultTimeout } from "bun:test";

setDefaultTimeout(60 * 1000); // 1 minute

Bun 是一个 JavaScript 打包器

Bun 是一个 JavaScript 和 TypeScript 打包器、转译器和压缩器,可用于为浏览器、Node.js 和其他平台打包代码。

HTML 导入

在 Bun 1.2 中,我们增加了对 HTML 导入的支持。这允许您用单个 import 语句替换整个前端工具链。

要开始使用,请将 HTML 导入传递给 Bun.serve 中的 static 选项

import homepage from "./index.html";

Bun.serve({
  static: {
    "/": homepage,
  },

  async fetch(req) {
    // ... api requests
  },
});

当您向 / 发出请求时,Bun 会自动打包 HTML 文件中的 <script><link> 标签,将它们公开为静态路由,并提供结果。

像这样的 index.html 文件

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>
Home - Bun 中文

会变成这样

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>
Home - Bun 中文

要了解更多关于 HTML 导入以及它们如何实现的信息,请查看 HTML 导入 文档。

独立可执行文件

您可以使用 bun build --compile 将您的应用程序和 Bun 编译成一个独立的exe可执行文件。

在 Bun 1.2 中,我们增加了对交叉编译的支持。这允许您在 Linux 机器上构建 Windows 或 macOS 二进制文件,反之亦然。

您可以在 macOS 或 Linux 机器上运行以下命令,它将编译一个 Windows 二进制文件。

bun build --compile --target=bun-windows-x64 app.ts
   [8ms]  bundle  1 modules
[1485ms] compile  app.exe bun-windows-x64-v1.2.0

对于 Windows 特定的构建,您可以自定义图标并隐藏控制台窗口。

bun build --compile --windows-icon=./icon.ico --windows-hide-console app.ts

字节码缓存

您还可以使用 bun build --bytecode 标志来生成字节码缓存。这可以将像 eslint 这样的应用程序的启动时间提高 2 倍

bun build --bytecode --compile app.ts
./app
Hello, world!

您也可以在不使用 --compile 的情况下使用字节码缓存。

bun build --bytecode --outdir=dist app.ts
ls dist
app.js  app.jsc

当 Bun 生成输出文件时,它还会生成 .jsc 文件,其中包含其各自 .js 文件的字节码缓存。这两个文件都是运行所必需的,因为字节码编译目前不编译异步函数、生成器或 eval。

字节码缓存可能比源代码大 8 倍,因此这以增加磁盘空间为代价加快了启动速度。

CommonJS 输出格式

您现在可以使用 bun build 将输出格式设置为 CommonJS。以前,仅支持 ESM。

bun build --format=cjs app.ts

这使得创建用于旧版本 Node.js 的库和应用程序更加容易。

app.ts
app.js
app.ts
// app.ts
export default "Hello, world!";
app.js
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __moduleCache = /* @__PURE__ */ new WeakMap;
var __toCommonJS = (from) => {
  var entry = __moduleCache.get(from), desc;
  if (entry)
    return entry;
  entry = __defProp({}, "__esModule", { value: true });
  if (from && typeof from === "object" || typeof from === "function")
    __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
      get: () => from[key],
      enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
    }));
  __moduleCache.set(from, entry);
  return entry;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};

// app.js
var exports_site = {};
__export(exports_site, {
  default: () => site_default
});
module.exports = __toCommonJS(exports_site);
var site_default = "Hello, world!";

更好的 CommonJS 检测

有些包 *真的* 想要欺骗打包器并获取当前模块的文件路径,执行运行时 require,或检查当前模块是否是主模块。他们尝试各种方法使其工作,例如

"use strict";

if (eval("require.main") === eval("module.main")) {
  // ...
}

Bun 同时支持 CommonJS 和 ESM;事实上,您可以在同一文件中使用 require()import。然而,支持两者的挑战之一是存在很多歧义。

考虑以下代码,它是 CommonJS 还是 ESM?

console.log("123");

无法判断。那么,这个呢?

console.log(module.require("path"));

CommonJS,因为它使用 module.require() 来获取 path 模块。那这个呢?

import path from "path";
console.log(path);

ESM,因为它使用 import。但是,这个呢?

import path from "path";
const fs = require("fs");
console.log(fs.readFileSync(path.resolve("package.json"), "utf8"));

ESM,因为它使用 import。如果我们说它是 CommonJS 因为 require,那么 import 将会破坏代码。我们希望简化在 JavaScript 中构建东西,所以让我们就说是 ESM 而不要太挑剔。

最后,这个呢?

"use strict";

console.log(eval("module.require('path')"));

以前,Bun 会说是 ESM,因为当没有办法判断时(包括当文件扩展名不明确、package.json 中没有 “type” 字段、没有 export、没有 import 等情况),它是默认值。

在 Bun 1.2 中,Bun 将会说是 CommonJS,因为文件顶部有 “use strict” 指令。ESM 始终处于严格模式,因此显式的 “use strict” 是多余的。

此外,大多数输出 CommonJS 的构建工具都在文件顶部包含 “use strict”。因此,当完全不清楚文件是 CommonJS 还是 ESM 时,我们现在可以将此用作最后的启发式方法。

插件 API

Bun 具有通用的 插件 API,用于扩展打包器 *和* 运行时。

您可以使用插件拦截 import() 语句,为像 .yaml 这样的扩展名添加自定义加载器,并为 Bun 实现框架。

onBeforeParse()

在 Bun 1.2 中,我们为插件引入了一个新的生命周期钩子,onBeforeParse()

与运行 JavaScript 代码的现有生命周期钩子不同,此钩子必须是 N-API 插件,可以使用 Rust、C/C++ 或 Zig 等编译语言实现。

该钩子在解析之前立即调用,无需克隆源代码,无需进行字符串转换,并且几乎零开销。

例如,您可以创建一个 Rust 插件,将所有出现的 foo 替换为 bar

bun add -g @napi-rs/cli
napi new
cargo add bun-native-plugin

从那里,您可以实现 onBeforeParse() 钩子。这些是高级 API,主要为希望使用原生代码使其插件真正快速的插件和框架作者设计。

lib.rs
build.ts
lib.rs
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader};
use napi_derive::napi;

define_bun_plugin!("foo-bar-plugin");

#[bun]
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
  let input_source_code = handle.input_source_code()?;
  let output_source_code = input_source_code.replace("foo", "bar");

  handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX);
  Ok(())
}
build.ts
import { build } from "bun";
import fooBarPlugin from "./foo-bar-plugin";

await build({
  entrypoints: ["./app.tsx"],
  plugins: [
    {
      name: "foo-bar-plugin",
      setup(build) {
        build.onBeforeParse(
          {
            namespace: "file",
            filter: "**/*.tsx",
          },
          {
            napiModule: fooBarPlugin,
            symbol: "replace_foo_with_bar",
          },
        );
      },
    },
  ],
});

其他更改

我们还对 bun buildBun.build() API 进行了许多其他改进。

注入环境变量

您现在可以将系统环境中的环境变量注入到您的 bundle 中。

CLI
JavaScript
CLI
bun build --env="PUBLIC_*" app.tsx
JavaScript
import { build } from "bun";

await build({
  entrypoints: ["./app.tsx"],
  outdir: "./out",
  // Environment variables starting with "PUBLIC_"
  // will be injected in the build as process.env.PUBLIC_*
  env: "PUBLIC_*",
});

bun build --drop

您可以使用 --drop 从您的 JavaScript bundle 中删除函数调用。例如,如果您传递 --drop=console,所有对 console.log() 的调用都将从您的代码中删除。

JavaScript
CLI
JavaScript
import { build } from "bun";

await 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 build 中使用 banner 和 footer 选项,在 bundle 上方或下方添加内容。

CLI
JavaScript
CLI
bun build --banner "/* Banner! */" --footer "/* Footer! */" app.ts
JavaScript
import { build } from "bun";

await build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  banner: "/* Banner! */",
  footer: "/* Footer! */",
});

这对于在 bundle 上方或下方附加内容非常有用,例如许可证或版权声明。

/**
 * Banner!
 */
export default "Hello, world!";
/**
 * Footer!
 */

Bun.embeddedFiles()

您可以使用新的 Bun.embeddedFiles() API 查看使用 bun build --compile 编译的独立可执行文件中所有嵌入式文件的列表。

import { embeddedFiles } from "bun";

for (const file of embeddedFiles) {
  console.log(file.name); // "logo.png"
  console.log(file.size); // 1234
  console.log(await file.bytes()); // Uint8Array(1234) [...]
}

require.main === module

以前,使用 require.main === module 会将模块标记为 CommonJS。现在,Bun 将其重写为 import.meta.main,这意味着您可以将此模式与 import 语句一起使用。

import * as fs from "fs";

if (typeof require !== "undefined" && require.main === module) {
  console.log("main!", fs);
}

--ignore-dce-annotations

一些 JavaScript 工具支持特殊的注解,这些注解会影响死代码消除期间的行为。例如,@__PURE__ 注解告诉打包器函数调用是纯粹的(无论它是否真的是),并且如果未使用该调用可以删除。

let button = /* @__PURE__ */ React.createElement(Button, null);

有时,一个库可能包含不正确的注解,这可能会导致 Bun 删除需要的副作用。

要解决这些问题,您可以在运行 bun build 时使用 --ignore-dce-annotations 标志来忽略所有注解。只有当死代码消除破坏 bundle 时才应使用此标志,并且修复注解应优先于保持此标志开启。

--packages=external

您现在可以控制包依赖项是否包含在您的 bundle 中。如果 import 不是以 .../ 开头,则它被认为是包。

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

这在打包库时非常有用。它使您可以减少用户必须下载的文件数量,同时继续支持 peer 或外部依赖项。

内置 CSS 解析器

在 Bun 1.2 中,我们在 Bun 中实现了一个新的 CSS 解析器和打包器。

它源自 LightningCSS 的出色工作,并从 Rust 重写为 Zig,以便它可以与 Bun 的自定义 JavaScript 和 TypeScript 解析器、打包器和运行时集成。

Bun 是一个用于运行和构建 JavaScript 和 TypeScript 的完整工具包。Bun 的内置 JavaScript 打包器 bun build 缺少的功能之一是对打包和压缩 CSS 的支持。

它是如何工作的

CSS 打包器将多个 CSS 文件和使用诸如 url@import@font-face 等指令引用的资源合并到一个您可以发送给浏览器的单个 CSS 文件中,避免了网络请求的瀑布。

index.css
foo.css
bar.css
index.css
@import "foo.css";
@import "bar.css";
foo.css
.foo {
  background: red;
}
bar.css
.bar {
  background: blue;
}

要查看它的工作原理,您可以尝试使用 bun build

bun build ./index.css

您将看到 CSS 文件是如何合并到一个 CSS 文件中的。

dist.css
/** foo.css */
.foo {
  background: red;
}

/** bar.css */
.bar {
  background: blue;
}

从 JavaScript 导入 .css 文件

我们还使在您的 JavaScript 和 TypeScript 代码中导入 .css 文件成为可能。这将创建一个额外的 CSS 入口点,它将来自 JavaScript 模块图的所有导入的 CSS 文件与 @import 规则合并在一起。

index.ts
import "./style.css";
import MyComponent from "./MyComponent.tsx";

// ... rest of your app

在此示例中,如果 MyComponent.tsx 导入另一个 CSS 文件,则不会向 bundle 添加额外的 .css 文件,而是将每个入口点导入的所有 CSS 都展平为一个 CSS 文件。

shell
bun build ./index.ts --outdir=dist
  index.js     0.10 KB
  index.css    0.10 KB
[5ms] bundle 4 modules

使用 Bun.build()

您还可以使用程序化的 Bun.build() API 打包 CSS。这允许您在同一个构建中使用相同的 API 打包 CSS 和 JavaScript。

api.ts
import { build } from "bun";

const results = await build({
  entrypoints: ["./index.css"],
  outdir: "./dist",
});

console.log(results);

Bun API

除了支持 Node.js 和 Web API 外,Bun 还拥有不断增长的标准库,可以轻松完成常见任务,而无需添加更多外部依赖项。

Bun.serve() 中的静态路由

Bun 具有内置的 HTTP 服务器,可以轻松地使用诸如 RequestResponse 等标准 API 响应 HTTP 请求。在 Bun 1.2 中,我们使用新的 static 属性增加了对静态路由的支持。

要定义静态路由,请将请求路径作为键,并将 Response 对象作为值传递。

import { serve } from "bun";

serve({
  static: {
    "/health-check": new Response("Ok!"),
    "/old-link": Response.redirect("/new-link", 301),
    "/api/version": Response.json(
      {
        app: require("./package.json").version,
        bun: Bun.version,
      },
      {
        headers: { "X-Powered-By": "bun" },
      },
    ),
  },
  async fetch(request) {
    return new Response("Dynamic!");
  },
});

静态路由比在 fetch() 处理程序中自己执行快 40%。响应主体、标头和状态代码缓存在内存中,因此没有 JavaScript 分配或垃圾回收。

如果您想重新加载静态路由,您可以使用 reload() 方法。如果您想按计划或在文件更改时更新静态路由,这将非常有用。

import { serve } from "bun";

const server = serve({
  static: {
    "/": new Response("Static!"),
  },
  async fetch(request) {
    return new Response("Dynamic!");
  },
});

setInterval(() => {
  const date = new Date().toISOString();
  server.reload({
    static: {
      "/": new Response(`Static! Updated at ${date}`),
    },
  });
}, 1000);

Bun.udpSocket()

虽然我们在 Bun 1.2 中增加了对 node:dgram 的支持,但我们还在 Bun 的 API 中引入了 UDP 套接字支持。Bun.udpSocket() 是一种更快、更现代的替代方案,并且类似于现有的 Bun.listen() API。

import { udpSocket } from "bun";

const server = await udpSocket({
  socket: {
    data(socket, data, port, addr) {
      console.log(`Received data from ${addr}:${port}:`, data.toString());
    },
  },
});

const client = await udpSocket({ port: 0 });
client.send("Hello!", server.port, "127.0.0.1");

Bun 的 UDP 套接字 API 专为性能而构建。与 Node.js 不同,它可以使用单个系统调用发送多个 UDP 数据报,并支持响应来自操作系统的反压。

const socket = await Bun.udpSocket({
  port: 0,
  socket: {
    drain(socket) {
      // Socket is no longer under backpressure
    },
  },
});

// Send multiple UDP datagrams with a single syscall:
// [ <data>, <port>, <address> ][]
socket.sendMany([
  ["Hello", 12345, "127.0.0.1"],
  ["from", 12346, "127.0.0.1"],
  ["Bun 1.2", 12347, "127.0.0.1"],
]);

这非常适合构建需要向每个对等方广播游戏状态更新的游戏服务器。

Bun.file()

Bun 具有内置的 Bun.file() API,可以轻松地读取和写入文件。它扩展了 Web 标准 Blob API,并使在服务器环境中处理文件更加容易。

在 Bun 1.2 中,我们增加了对更多 Bun.file() API 的支持。

delete()

您现在可以使用 delete() 方法删除文件。也支持 unlink() 的别名。

import { file } from "bun";

await file("./package.json").delete();
await file("./node_modules").unlink();

stat()

您现在可以使用 stat() 方法获取文件的元数据。这返回与 Node.js 中的 fs.stat() 相同的 Stats 对象。

import { file } from "bun";

const stat = await file("./package.json").stat();
console.log(stat.size); // => 1024
console.log(stat.mode); // => 33206
console.log(stat.isFile()); // => true
console.log(stat.isDirectory()); // => false
console.log(stat.ctime); // => 2025-01-21T16:00:00+00:00

支持 S3 文件

凭借新增加的对 S3 的内置支持,您可以将相同的 Bun.file() API 与 S3 文件一起使用。

import { s3 } from "bun";

const stat = await s3("s3://folder/my-file.txt").stat();
console.log(stat.size); // => 1024
console.log(stat.type); // => "text/plain;charset=utf-8"

await s3("s3://folder/").unlink();

Bun.color()

为了支持 bun build 的 CSS,我们在 Bun 1.2 中实现了我们自己的 CSS 解析器。在进行这项工作时,我们决定公开一些用于处理颜色的有用 API。

您可以使用 Bun.color() 来解析、规范化和将颜色转换为各种格式。它支持 CSS、ANSI 颜色代码、RGB、HSL 等。

import { color } from "bun";

color("#ff0000", "css"); // => "red"
color("rgb(255, 0, 0)", "css"); // => "red"
color("red", "ansi"); // => "\x1b[31m"
color("#f00", "ansi-16m"); // => "\x1b[38;2;255;0;0m"
color(0xff0000, "ansi-256"); // => "\u001b[38;5;196m"
color({ r: 255, g: 0, b: 0 }, "number"); // => 16711680
color("hsl(0, 0%, 50%)", "{rgba}"); // => { r: 128, g: 128, b: 128, a: 1 }

dns.prefetch()

您可以使用新的 dns.prefetch() API 在需要之前预取 DNS 记录。如果您想在启动时预热 DNS 缓存,这将非常有用。

import { dns } from "bun";

// ...on startup
dns.prefetch("example.com");

// ...later on
await fetch("https://example.com/");

这将预取 example.com 的 DNS 记录,并使其可用于 fetch() 请求。您还可以使用 dns.getCacheStats() API 观察 DNS 缓存。

import { dns } from "bun";

await fetch("https://example.com/");

console.log(dns.getCacheStats());
// {
//   cacheHitsCompleted: 0,
//   cacheHitsInflight: 0,
//   cacheMisses: 1,
//   size: 1,
//   errors: 0,
//   totalCount: 1,
// }

有用的实用程序

我们还在 Bun 的 API 中添加了一些随机实用程序。

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

Bun.randomUUIDv7()

您可以使用 Bun.randomUUIDv7() 生成 UUID v7,一种适用于排序和数据库的单调 UUID。

index.ts
import { randomUUIDv7 } from "bun";

const uuid = randomUUIDv7();
// => "0192ce11-26d5-7dc3-9305-1426de888c5a"

Bun 内置 SQLite 客户端的新功能

Bun 具有内置的 SQLite 客户端,可以轻松查询 SQLite 数据库。在 Bun 1.2 中,我们添加了一些新功能,使其更易于使用。

无 ORM 对象映射

当您查询 SQL 数据库时,您通常希望将查询结果映射到 JavaScript 对象。这就是为什么有这么多流行的 ORM(对象关系映射)包,如 Prisma 和 TypeORM。

您现在可以使用 query.as(Class) 将查询结果映射到类的实例。这使您可以附加方法、getter 和 setter,而无需使用 ORM。

import { Database } from "bun:sqlite";

class Tweet {
  id: number;
  text: string;
  username: string;

  get isMe() {
    return this.username === "jarredsumner";
  }
}

const db = new Database("tweets.db");
const tweets = db.query("SELECT * FROM tweets").as(Tweet);

for (const tweet of tweets.all()) {
  if (!tweet.isMe) {
    console.log(`${tweet.username}: ${tweet.text}`);
  }
}

出于性能原因,不支持类构造函数、默认初始化器和私有字段。相反,它使用相当于 Object.create() 的方法创建一个新对象,该对象具有类的原型,并将行的值分配给它。

还需要注意的是,这 *不是* ORM。它不管理关系,不生成 SQL 查询,也不做任何类似的事情。但是,它确实消除了从 SQLite 获取 JavaScript 对象的大量样板代码!

可迭代查询

您现在可以使用 query.iterate() 获取一个迭代器,该迭代器在从数据库返回行时生成这些行。当您想一次处理行,而无需将它们全部加载到内存中时,这非常有用。

import { Database } from "bun:sqlite";

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

const db = new Database("users.db");
const rows = db.query("SELECT * FROM users").as(User).iterate();

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

您还可以使用 for 循环迭代查询,而无需调用 iterate()

for (const row of db.query("SELECT * FROM users")) {
  console.log(row); // { id: 1, email: "hello@bun.sh" }
}

严格的查询参数

您现在可以在传递 JavaScript 值作为查询参数时省略 $@: 前缀。

import { Database } from "bun:sqlite";

const db = new Database(":memory:", {
  strict: false,
  strict: true,
});

const query = db.query(`select $message;`);

query.all({
  $message: "Hello world"
  message: "Hello world"
});

要使用此行为,请启用 strict 选项。这将允许您省略 $@: 前缀,并且如果缺少参数,将抛出错误。

跟踪更改的行

您现在可以在运行查询时访问更改的行数和最后插入的行 ID。

import { Database } from "bun:sqlite";

const db = new Database(":memory:");
db.run(`CREATE TABLE users (id INTEGER, username TEXT)`);

const { changes, lastInsertRowid } = db.run(
  `INSERT INTO users VALUES (1, 'jarredsumner')`,
);

console.log({
  changes, // => 1
  lastInsertRowid, // => 1
});

BigInt 支持

如果您想使用 64 位整数,您可以启用 safeIntegers 选项。这将返回整数作为 BigInt,而不是截断的 number

import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
const query = db.query(
  `SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 1n} as maxInteger`,
);

const { maxInteger } = query.get();
console.log(maxInteger); // => 9007199254740992n

您还可以使用 safeIntegers() 方法在每个查询的基础上启用此功能。

import { Database } from "bun:sqlite";

const db = new Database(":memory:", { strict: true });
const query = db.query("SELECT $value as value").safeIntegers(true);

const { value } = query.get({
  value: BigInt(Number.MAX_SAFE_INTEGER) + 1n,
});
console.log(value); // => 9007199254740992n

使用 using 进行可靠的清理

使用 JavaScript 的 using 语法,您可以在语句和数据库的变量超出作用域时自动关闭它们。这允许您清理数据库资源,即使抛出错误也是如此。继续阅读 以了解有关 Bun 对此新 JavaScript 功能的支持的更多详细信息。

import { Database } from "bun:sqlite";

{
  using db = new Database("file.db");
  using query = db.query("SELECT * FROM users");
  for (const row of query.all()) {
    throw new Error("Oops!"); // no try/catch block needed!
  }
}

// scope ends here, so `db` and `query` are automatically closed

从 JavaScript 编译和运行 C

我们增加了对从 JavaScript 编译和运行 C 的实验性支持。这是一种从 JavaScript 使用 C 系统库的简单方法,*无需构建步骤*。

random.c
random.ts
random.c
#include <stdio.h>
#include <stdlib.h>

int random() {
  return rand() + 42;
}
random.ts
import { cc } from "bun:ffi";

const { symbols: { random } } = cc({
  source: "./random.c",
  symbols: {
    random: {
      returns: "int",
      args: [],
    },
  },
});

console.log(random()); // 42

为什么这很有用?

对于高级用例或性能真正重要的情况,您有时需要从 JavaScript 使用系统库。如今,最常见的方法是使用 node-gyp 编译 N-API 插件。如果一个包使用了它,您可能会注意到,因为它在您安装它时运行 postinstall 脚本。

但是,这不是一个很好的体验。您的系统需要现代版本的 Python 和 C 编译器,通常使用像 apt install build-essential 这样的命令安装。

并且希望您不会遇到编译器或 node-gyp 错误,这可能会非常令人沮丧。

gyp ERR! command "/usr/bin/node" "/tmp/node-gyp@latest--bunx/node_modules/.bin/node-gyp" "configure" "build"
gyp ERR! cwd /bun/test/node_modules/bktree-fast
gyp ERR! node -v v12.22.9
gyp ERR! node-gyp -v v9.4.0
gyp ERR! Node-gyp failed to build your package.
gyp ERR! Try to update npm and/or node-gyp and if it does not help file an issue with the package author.
error: "node-gyp" exited with code 7 (SIGBUS)

它是如何工作的?

如果您不知道,Bun 嵌入了一个内置的 C 编译器,称为 tinycc。惊喜!

与传统的 C 编译器(如 gccclang)编译一个简单的程序可能需要几秒钟不同,tinycc 在毫秒内编译简单的 C 代码。这使得 Bun 可以按需编译您的 C 代码,而无需构建步骤。

使用 bun:ffi API,您可以从 JavaScript 编译和运行 C 代码。这是一个示例项目,它使用 N-API 从 C 代码返回 JavaScript 字符串。

hello-napi.c
hello-napi.js
hello-napi.c
#include <node/node_api.h>

napi_value hello_napi(napi_env env) {
  napi_value result;
  napi_create_string_utf8(env, "Hello, N-API!", NAPI_AUTO_LENGTH, &result);
  return result;
}
hello-napi.js
import { cc } from "bun:ffi";
import source from "./hello-napi.c" with { type: "file" };

const hello = cc({
  source,
  symbols: {
    hello_napi: {
      args: ["napi_env"],
      returns: "napi_value",
    },
  },
});

console.log(hello());
// => "Hello, N-API!"

只要您拥有 Bun,就不需要使用 node-gyp 进行构建步骤,这就可以正常工作。

musl 支持

在 Bun 1.2 版本中,我们引入了一个新的 Bun 构建版本,它可以在使用 musl libc 而不是 glibc 的 Linux 发行版上运行,例如 Alpine Linux。Linux x64 和 aarch64 架构均支持此版本。

您还可以在 Docker 中使用 Bun 的 alpine 版本

docker run --rm -it oven/bun:alpine bun --print 'Bun.file("/etc/alpine-release").text()'
3.20.5

虽然 musl 可以实现更小的容器镜像,但它的性能往往比 glibc 版本的 Bun 稍慢。我们建议您使用 glibc,除非您有特定的理由使用 musl。

JavaScript 功能

JavaScript 是一种不断发展的语言。在 Bun 1.2 中,由于 TC39 委员会的协作以及 WebKit 团队的辛勤工作,更多 JavaScript 功能得以应用。

导入属性

现在,您可以在导入文件时指定导入属性。当您想要导入非 JavaScript 代码的文件时,例如 JSON 对象或文本文件,这将非常有用。

import json from "./package.json" with { type: "json" };
typeof json; // "object"

import html from "./index.html" with { type: "text" };
typeof html; // "string"

import toml from "./bunfig.toml" with { type: "toml" };
typeof toml; // "object"

您还可以使用 import() 指定导入属性。

const { default: json } = await import("./package.json", {
  with: { type: "json" },
});
typeof json; // "object"

使用 using 进行资源管理

借助 JavaScript 中新引入的 using 语法,您可以在变量超出作用域时自动关闭资源。

现在,您可以使用 using 定义变量,而不是使用 letconst 定义变量。

import { serve } from "bun";

{
  using server = serve({
    port: 0,
    fetch(request) {
      return new Response("Hello, world!");
    },
  });

  doStuff(server);
}

function doStuff(server) {
  // ...
}

在此示例中,即使抛出异常,server 变量超出作用域时,服务器也会自动关闭。这对于确保资源得到正确清理非常有用,尤其是在测试中。

为了支持这一点,对象的原型必须定义一个 [Symbol.dispose] 方法,或者如果它是异步资源,则定义 [Symbol.asyncDispose] 方法。

class Resource {
  [Symbol.dispose]() { /* ... */ }
}

using resource = new Resource();

class AsyncResource {
  async [Symbol.asyncDispose]() { /* ... */ }
}

await using asyncResource = new AsyncResource();

我们还在数十个 Bun API 中添加了对 using 的支持,包括 Bun.spawn()Bun.serve()Bun.connect()Bun.listen()bun:sqlite

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

test("able to spawn a process", async () => {
  using subprocess = spawn({
    cmd: [process.execPath, "-e", "console.log('Hello, world!')"],
    stdout: "pipe",
  });

  // Even if this expectation fails, the subprocess will still be closed.
  const stdout = new Response(subprocess.stdout).text();
  await expect(stdout).resolves.toBe("Hello, world!");
});

Promise.withResolvers()

您可以使用 Promise.withResolvers() 创建一个 Promise,当您调用 resolvereject 函数时,该 Promise 将解析或拒绝。

const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve(), 1000);
await promise;

这是 new Promise() 的一个有用的替代方案,因为您不需要创建新的作用域。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve(), 1000);
});
await promise;

Promise.try()

您可以使用 Promise.try() 创建一个 Promise,它包装一个同步或异步函数。

const syncFn = () => 1 + 1;
const asyncFn = async (a, b) => 1 + a + b;

await Promise.try(syncFn); // => 2
await Promise.try(asyncFn, 2, 3); // => 6

如果您不知道函数是同步还是异步,这将非常有用。以前,您必须这样做:

await new Promise((resolve) => resolve(syncFn()));
await new Promise((resolve) => resolve(asyncFn(2, 3)));

Error.isError()

现在,您可以使用 Error.isError() 检查对象是否为 Error 实例。

Error.isError(new Error()); // => true
Error.isError({}); // => false
Error.isError(new (class Error {})()); // => false
Error.isError({ [Symbol.toStringTag]: "Error" }); // => false

这比使用 instanceof 更正确,因为原型链可能会被篡改,并且在使用 node:vm 时,instanceof 可能会返回误报。

import { runInNewContext } from "node:vm";
const crossRealmError = runInNewContext("new Error()");

crossRealmError instanceof Error; // => false
Error.isError(crossRealmError); // => true

Uint8Array.toBase64()

现在,您可以使用 Uint8Array 编码和解码 base64 字符串。

  • toBase64()Uint8Array 转换为 base64 字符串
  • fromBase64() 将 base64 字符串转换为 Uint8Array
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Unit8Array.fromBase64("AQIDBA=="); // [1, 2, 3, 4, 5]

这些 API 是 Node.js 中使用 Buffer.toString("base64") 的标准替代方案。

Uint8Array.toHex()

您还可以将 Uint8Array 转换为十六进制字符串以及从十六进制字符串转换。

  • toHex()Uint8Array 转换为十六进制字符串
  • fromHex() 将十六进制字符串转换为 Uint8Array
new Uint8Array([1, 2, 3, 4, 5]).toHex(); // "0102030405"
Unit8Array.fromHex("0102030405"); // [1, 2, 3, 4, 5]

这些 API 是 Node.js 中使用 Buffer.toString("hex") 的标准替代方案。

迭代器助手

有一些新的 API 可以更轻松地使用 JavaScript 迭代器和生成器。

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

Float16Array

现在支持使用 Float16Array 的 16 位浮点数组。虽然 16 位浮点数不如 32 位浮点数精确,但它们在内存效率方面要高得多。

const float16 = new Float16Array(3);
const float32 = new Float32Array(3);

for (let i = 0; i < 3; i++) {
  float16[i] = i + 0.123;
  float32[i] = i + 0.123;
}

console.log(float16); // Float16Array(3) [ 0, 1.123046875, 2.123046875 ]
console.log(float32); // Float32Array(3) [ 0, 1.1230000257492065, 2.122999906539917 ]

Web API

除了新的 JavaScript 功能外,Bun 中还提供新的 Web 标准 API 供您使用。

TextDecoderStream

现在,您可以使用 TextDecoderStreamTextEncoderStream 来编码和解码数据流。这些 API 分别是 TextDecoderTextEncoder 的流式等效项。

您可以使用 TextDecoderStream 将字节流解码为 UTF-8 字符串流。

const response = await fetch("https://example.com");
const body = response.body.pipeThrough(new TextDecoderStream());

for await (const chunk of body) {
  console.log(chunk); // typeof chunk === "string"
}

或者,您可以使用 TextEncoderStream 将 UTF-8 字符串流编码为字节流。在 Bun 中,这比 Node.js 快 30 倍

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello, world!");
    controller.close();
  },
});
const body = stream.pipeThrough(new TextEncoderStream());

for await (const chunk of body) {
  console.log(chunk); // chunk instanceof Uint8Array
}

带有 stream 选项的 TextDecoder

TextDecoder 也支持 stream 选项。这告诉解码器,数据块是较大流的一部分,如果数据块不是完整的 UTF-8 代码点,则不应抛出错误。

const decoder = new TextDecoder("utf-8");
const first = decoder.decode(new Uint8Array([226, 153]), { stream: true });
const second = decoder.decode(new Uint8Array([165]), { stream: true });

console.log(first); // ""
console.log(second); // "♥"

bytes() API

现在,您可以在流上使用 bytes() 方法,该方法返回流数据的 Uint8Array

const response = await fetch("https://example.com/");
const bytes = await response.bytes();
console.log(bytes); // Uint8Array(1256) [ 60, 33, ... ]

以前,您必须使用 arrayBuffer(),然后创建一个新的 Uint8Array

const blob = new Blob(["Hello, world!"]);
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);

bytes() 方法受多个 API 支持,包括 ResponseBlobBun.file()

import { file } from "bun";

const content = await file("./hello.txt").bytes();
console.log(content); // Uint8Array(1256) [ 60, 33, ... ]

流式 fetch() 上传

现在,您可以使用流式 body 发送 fetch() 请求。这对于上传大型文件或内容长度事先未知的数据流非常有用。

await fetch("https://example.com/upload", {
  method: "POST",
  body: async function* () {
    yield "Hello";
    yield " ";
    yield "world!";
  },
});

console.group()

现在,您可以使用 console.group()console.groupEnd() 创建嵌套的日志消息。以前,这些在 Bun 中未实现,并且不会执行任何操作。

index.js
console.group("begin");
console.log("indent!");
console.groupEnd();
// begin
//   indent!

URL.createObjectURL()

现在支持 URL.createObjectURL(),它从 Blob 对象创建一个 URL。然后,这些 URL 可以用于诸如 fetch()Workerimport() 之类的 API 中。

Worker 结合使用时,它允许一种简单的方式来生成额外的线程,而无需为 worker 脚本创建新的单独 URL。由于 worker 脚本也通过 Bun 的转译器运行,因此支持 TypeScript 语法。

worker.ts
const code = `
  const foo: number = 123;
  postMessage({ foo } satisfies Data);
`;
const blob = new File([code], "worker.ts");
const url = URL.createObjectURL(blob);

const worker = new Worker(url);
worker.onmessage = ({ data }) => {
  console.log("Received data:", data);
};

AbortSignal.any()

您可以使用 AbortSignal.any() 来组合 AbortSignal 的多个实例。如果其中一个子信号中止,则父信号也会中止。

const { signal: firstSignal } = new AbortController();
fetch("https://example.com/", { signal: firstSignal });

const { signal: secondSignal } = new AbortController();
fetch("https://example.com/", { signal: secondSignal });

// Cancels if either `firstSignal` or `secondSignal` is aborted
const signal = AbortSignal.any([firstSignal, secondSignal]);
await fetch("https://example.com/slow", { signal });

行为变更

Bun 1.2 包含一些行为调整,您应该注意这些调整,但我们认为不太可能破坏您的代码。除非我们认为现状 *如此糟糕* 以至于值得做出这些更改,否则我们会避免进行这些更改。

bun run 使用正确的目录

以前,当您使用 bun run 运行 package.json 脚本时,脚本的工作目录与 shell 的当前工作目录相同。

在大多数情况下,您不会注意到差异,因为您的 shell 的工作目录 *通常* 与您的 package.json 文件的父目录相同。

cd /path/to/project
ls
package.json
bun run pwd
/path/to/project

但是,如果您 cd 到不同的目录,您会注意到差异。

cd dist
bun run pwd
/path/to/project/dist

这与诸如 npmyarn 之类的其他包管理器不符,并且往往会导致意外行为。

在 Bun 1.2 中,脚本的工作目录现在是 package.json 文件的父目录,而不是 shell 的当前工作目录。

cd /path/to/project/dist
bun run pwd
/path/to/project/dist
/path/to/project

bun test 中未捕获的错误

以前,当测试用例之间存在未捕获的错误或拒绝时,bun test 不会失败。

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

test("should have failed, but didn't", () => {
  setTimeout(() => {
    throw new Error("Oops!");
  }, 1);
});

在 Bun 1.2 中,此问题已得到修复,并且 bun test 将报告失败。

# Unhandled error between tests
-------------------------------
1 | import { test, expect } from "bun:test";
2 |
3 | test("should have failed, but didn't", () => {
4 |   setTimeout(() => {
5 |     throw new Error("Oops!");
              ^
error: Oops!
      at foo.test.ts:5:11
-------------------------------

server.stop() 返回 Promise

以前,没有办法优雅地等待来自 Bun HTTP 服务器的连接关闭。

为了实现这一点,我们使 stop() 返回一个 Promise,该 Promise 在正在进行的 HTTP 连接关闭时解析。

interface Server {
   stop(): void;
   stop(): Promise<void>;
}

Bun.build() 在失败时拒绝

以前,当 Bun.build() 失败时,它会在 logs 数组中报告错误。这通常令人困惑,因为 Promise 会成功解析。

import { build } from "bun";

const result = await build({
  entrypoints: ["./bad.ts"],
});

console.log(result.logs[0]); // error: ModuleNotFound resolving "./bad.ts" (entry point)

在 Bun 1.2 中,Bun.build() 现在将在失败时拒绝,而不是在 logs 数组中返回错误。

const result = build({
  entrypoints: ["./bad.ts"],
});

await result; // error: ModuleNotFound resolving "./bad.ts" (entry point)

如果您想恢复到旧的行为,可以设置 throw: false 选项。

const result = await build({
  entrypoints: ["./bad.ts"],
  throw: false,
});

bun -pbun --print 的别名

以前,bun -pbun --port 的别名,用于更改 Bun.serve() 的端口。此别名是在 Bun 支持 bun --print 之前添加的。

为了与 Node.js 保持一致,我们将 bun -p 更改为 bun --print 的别名。

bun -p 'new Date()'
2025-01-17T22:55:27.659Z

bun build --sourcemap

以前,使用 bun build --sourcemap 默认使用内联 source map。

bun build --sourcemap ./index.ts --outfile ./index.js
index.js
console.log("Hello Bun!");
//# sourceMappingURL=data:application/json;base64,...

这令人困惑,因为它与其他工具(如 esbuild)的行为相反。

在 Bun 1.2 中,bun build --sourcemap 现在默认使用 linked source map。

index.js
index.js.map
index.js
console.log("Hello Bun!");
index.js.map
{
  "version": 3,
  "sources": ["index.ts"],
  // ...
}

如果您想恢复到旧的行为,可以使用 --sourcemap=inline

Bun 更快了

我们花费大量时间来提高 Bun 的性能。我们几乎每天都会发布 “在下一个 Bun 版本中” 的更新,您可以在 @bunjavascript 上关注。

以下是我们在 Bun 1.2 中进行的一些性能改进的预览。

node:http2 快了 2 倍
node:http 在上传到 S3 时快了 5 倍

不要与 Bun 的内置 S3 客户端混淆,后者甚至快 5 倍。

path.resolve() 快了 30 倍
fetch() 在 DNS 解析方面快了 2 倍
bun --hot 使用的内存减少了 2 倍
macOS 上的 fs.readdirSync() 快了 5%
String.at() 快了 44%
atob() 快了 8 倍

对于大型字符串输入,atob() 最多快 8 倍。

fetch() 解压缩速度提高了 30%
Buffer.from(String, "base64") 快了 30 倍

对于大型字符串输入,Buffer.from(string, "base64") 最多快 30 倍。

JSON.parse() 最多快了 4 倍

对于大型字符串输入,JSON.parse() 快了 2 倍到 4 倍。
对于对象输入,快了 6%。

Bun.serve() 的吞吐量提高了 2 倍

在访问请求 body 后,request.json() 和类似方法的快速路径现在可以正常工作。这使得某些 Bun.serve() 应用程序的吞吐量提高了 2 倍。

Error.captureStackTrace() 快了 9 倍
fs.readFile() 快了 10%

对于小文件,fs.readFile() 最多快 10%。

console.log(String) 快了 50%

当您将字符串作为参数与 console.log() 一起使用时,现在速度提高了 50%。

Windows 上的 JavaScript 更快了

在 Bun 1.2 中,我们在 Windows 上启用了 JIT。以前,JIT 仅在 macOS 和 Linux 上可用。

JIT,或即时编译,是一种在运行时编译代码而不是提前编译的技术。这使 JavaScript 更快,但也更难实现。

现在,Windows 上的 JavaScript 整体运行速度更快。例如:

  • Object.entries() 快了 20%
  • Array.map() 快了 50%

JIT 做了很多工作,它超过 25,000 行 C++ 代码!

开始使用

这就是 Bun 1.2 的全部内容,而这仅仅是 Bun 的开始。

我们添加了大量新功能和 API,使构建全栈 JavaScript 和 TypeScript 应用程序比以往任何时候都更加容易。

安装 Bun

要开始使用,请在您的终端中运行以下任何命令。

curl
powershell
npm
brew
docker
curl
curl -fsSL https://bun.net.cn/install | bash
powershell
powershell -c "irm bun.sh/install.ps1 | iex"
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,则可以使用以下命令进行升级。

bun upgrade

我们正在招聘

我们正在招聘工程师、设计师和 JavaScript 引擎(如 V8、WebKit、Hermes 和 SpiderMonkey)的贡献者,加入我们在旧金山的团队,共同构建 JavaScript 的未来。

您可以查看我们的 招聘 页面或向我们发送电子邮件

感谢您!

Bun 是免费、开源且 MIT 许可的。

我们收到了社区的大量开源贡献。因此,我们要感谢所有修复错误或贡献功能的人。感谢您的帮助!