为什么是 Bun

Node.js 生态经过十几年演化,形成了一条“缝合”的工具链:npm/yarn/pnpm 装包、esbuild/swc 转译、jest/vitest 测试、tsc 类型检查、nodemon 热重载、dotenv 配置。每多一层,就多一份冷启动成本和配置心智负担。

Bun 用 Zig 编写、基于 JavaScriptCore,把包管理器、运行时、转译器、测试运行器、打包器塞进了一个二进制。它的口号很直白:让 JS 项目“开箱即跑”。

安装与初始化

# macOS / Linux
curl -fsSL https://bun.sh/install | bash

# Windows
powershell -c "irm bun.sh/install.ps1 | iex"

bun --version
bun init -y

bun init 生成的 bunfig.toml 就是全部配置,没有 package-lock.json 之外的第二套锁文件烦恼。

用 Bun 写 HTTP 服务:内置 API 足够好用

// server.ts
import { sql } from "bun";

const server = Bun.serve({
  port: 3000,
  routes: {
    "/": () => new Response("hello bun"),

    "/users/:id": async (req) => {
      const id = Number(req.params.id);
      const [user] = await sql`select id, name from users where id = ${id}`;
      if (!user) return new Response("not found", { status: 404 });
      return Response.json(user);
    },

    "/upload": async (req) => {
      const form = await req.formData();
      const file = form.get("file") as File;
      await Bun.write(`./uploads/${file.name}`, file);
      return Response.json({ ok: true, size: file.size });
    },
  },

  // 全局错误兜底
  error(error) {
    console.error(error);
    return new Response("internal error", { status: 500 });
  },
});

console.log(`listening on ${server.port}`);

注意几个 Bun 独有能力:

  • Bun.serve 的 routes:无需引入 Express/Hono 就能做路由与参数解析;
  • Bun.sql:内置 PostgreSQL 客户端,带连接池与标签模板防注入;
  • File 是一等公民:Bun.write 直接写磁盘,流式处理大文件不占内存。

测试:兼容 Jest 语法但快得多

// user.test.ts
import { expect, test, beforeEach, mock } from "bun:test";
import { createUser } from "./user";

beforeEach(() => {
  mock.restore();
});

test("createUser 校验邮箱", () => {
  expect(() => createUser({ email: "bad" })).toThrow(/email/i);
});

test("createUser 返回 id", () => {
  const u = createUser({ email: "a@b.com" });
  expect(u.id).toBeString();
});
bun test              # 跑全部
bun test --watch      # 监听
bun test --coverage   # 覆盖率

构建与部署

# 打包成单文件可执行程序(含运行时)
bun build ./server.ts --compile --target=bun-linux-x64 --outfile app

# 或者产出静态产物
bun build ./src/index.tsx --outdir dist --target browser

--compile 会生成一个几十 MB 的自包含二进制,部署到没有 Node 的机器上也能直接跑,冷启动通常在 10ms 级别。

Docker 场景可以用官方极小镜像:

FROM oven/bun:1-alpine
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD ["bun", "run", "server.ts"]

bun install 的安装速度通常比 npm 快 5~20 倍,CI 上收益非常明显。

迁移注意事项

  1. Node API 兼容度已很高,但依赖 node:v8、部分 worker_threads 细节的库仍可能踩坑,先跑一遍测试。
  2. 不要用 bun run 跑关键的类型检查,Bun 只做转译不做检查,保留 tsc --noEmit 或改用 tsgo。
  3. 锁文件:Bun 2.x 默认生成文本格式的 bun.lock,可直接 diff,建议在 CI 里使用 --frozen-lockfile。

小结

Bun 的价值不只是“快”,而是把工具链从十几个包压缩成一个二进制。对于新项目、CLI 工具、边缘函数这类场景,它已经完全可以作为首选运行时。