第五章 怎么用

第五章 怎么用

欢迎来到 Bun 的"灵魂拷问"环节——前四章我们把 Bun 从头到脚介绍了一遍,现在终于要上手"干活"了。本章是 Bun 的实战指南,覆盖从安装到生产的完整流程,保证你学完就能跑。

如果你是从 Node.js 切换过来的,别担心——Bun 的口号是"零成本迁移",我们会在每个环节证明这不是吹牛。


5.1 安装 Bun

安装 Bun 是你踏上 Bun 之旅的第一步,也是唯一需要"安装"的步骤——之后所有的工具都是 Bun 自带的!就像买了一把瑞士军刀,打开盒子那一刻,所有刀片都在里面了。

Windows 安装

1
2
3
4
5
# PowerShell 一行命令搞定(推荐)
irm bun.sh/install.ps1 | iex

# 或者手动下载安装
# 下载地址:https://github.com/oven-sh/bun/releases

Windows 要求 Windows 10 版本 1809 或更高。如果安装后提示"command not found",需要把 C:\Users\你的用户名\.bun\bin 添加到系统 PATH 环境变量里——这大概是整个安装过程中最"坑"的一步。

macOS 安装

1
2
3
4
5
# Homebrew(推荐)
brew install oven-sh/bun/bun

# 或者官方脚本
curl -fsSL https://bun.com/install | bash

Linux 安装

1
curl -fsSL https://bun.com/install | bash

Linux 需要 unzip 包:sudo apt install unzip。内核建议 5.6+,最低 5.1。CentOS/RHEL 用户:sudo yum install unzip

npm 安装

1
npm install -g bun

Bun 官方说这是"你最后一个需要全局安装的 npm 包"——因为装完 Bun 之后,你再也不需要 npm install 全局包了。Bun 自己会帮你搞定一切。

Docker 安装

1
2
3
4
5
6
7
8
# 标准镜像
docker pull oven/bun

# 运行
docker run --rm --init oven/bun bun --version

# 带项目的运行方式
docker run --rm -it -v $(pwd):/app -w /app oven/bun bun install

Docker 镜像变体:oven/bun:debianoven/bun:slimoven/bun:alpineoven/bun:distroless。追求小体积?选 alpine;追求兼容性?选 debian。

升级 Bun

1
2
3
4
5
6
7
8
# 升级到最新稳定版
bun upgrade

# 升级到最新测试版(canary)
bun upgrade --canary

# 切回稳定版
bun upgrade --stable

Homebrew 用户用 brew upgrade bun,Scoop 用户用 scoop update bun

验证安装

1
2
bun --version     // 1.3.x(当前稳定版)
bun --revision    // 1.3.x+xxxxxxxxxxxx(精确 git commit hash)

版本号会随着发布更新,以上只是示例格式。如果看到版本号,说明一切正常——恭喜你,正式成为 Bun 用户!

卸载 Bun

1
2
3
4
5
# macOS / Linux
rm -rf ~/.bun

# Windows:运行卸载脚本
# 在文件管理器打开 %USERPROFILE%\.bun\ 目录,双击 uninstall.ps1

卸载前请三思——你确定要和一个启动速度比 Node 快 4 倍的运行时说再见吗?


5.2 包管理命令

Bun 的包管理命令和 npm 几乎一样,迁移成本为零。如果你用过 npm,看到下面的命令会有一种"这不就是换了个前缀"的快感。

安装依赖

1
2
3
4
5
6
7
8
# 安装所有依赖(等价于 npm install)
bun install

# 只安装生产依赖(跳过 devDependencies)
bun install --production

# CI 常用:锁定文件不允许变更(等价于 npm ci)
bun install --frozen-lockfile

添加包

1
2
3
4
5
# 安装并写入 package.json(等价于 npm install xxx)
bun add react react-dom

# 安装为开发依赖(等价于 npm install -D xxx)
bun add -d typescript @types/react

删除包

1
2
# 删除包并从 package.json 移除(等价于 npm uninstall xxx)
bun remove react

更新包

1
2
bun update              # 更新所有包(等价于 npm update)
bun update vite         # 更新特定包

查看包信息

1
2
3
4
5
6
7
8
# 查看依赖树(等价于 npm ls)
bun pm ls               # 查看已安装的包

# 查看包详情(等价于 npm info)
bun info react

# 安全审计(等价于 npm audit)
bun audit

bun list 在某些旧版是别名,但新版推荐用 bun pm ls


5.3 运行脚本

这一节是你最常用的部分——写完代码就要跑,跑了就要改,改完再跑。

运行 package.json 里的脚本

1
2
3
4
bun run dev        # 运行 dev 脚本
bun run build      # 运行 build 脚本
bun run test       # 运行 test 脚本
bun run            # 不带参数,列出所有可用脚本

直接运行文件

1
2
3
bun run index.ts        # 运行 TypeScript 文件
bun index.tsx           # 裸命令,等价于 bun run
bun index.js            # 直接运行 JS 文件

裸命令(如 bun index.tsx)和 bun run index.tsx 完全等效,只是少敲了几个字——懒人福利。

从模板创建项目

1
2
3
4
bun create react my-app       # React 项目模板
bun create library my-lib     # 类库项目模板
bun create blank my-project   # 空白项目
bun create https://example.com/template.zip  # 从 URL 创建

不安装直接运行包(bunx)

1
bunx cowsay "Hello from Bun!"  # 等价于 npx,但快很多(预热后基本无延迟)

bunxbun execute 的别名,第一次运行会缓存,不用每次都下载。

从 stdin 运行代码

1
2
# 相当于一个临时的小脚本执行器
echo "console.log('Hello!')" | bun run -

Shebang 脚本

1
2
#!/usr/bin/env bun
console.log("这是一个独立运行的脚本!");

保存为 script.ts,赋予执行权限后直接运行:

1
chmod +x script.ts && ./script.ts

Shebang 脚本在 Unix 系统下可以直接双击运行——比打开终端敲命令优雅多了。


5.4 TypeScript 支持

Bun 对 TypeScript 的支持是内置的、零配置的。你不需要装 ts-node、不需要配置 tsconfig、不需要任何额外步骤——把 .js 改成 .ts,然后直接跑。

无需配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// greeter.ts
interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return `你好,${user.name}!今年${user.age}岁了。`;
}

const user: User = { name: "张三", age: 25 };
console.log(greet(user));
1
2
bun run greeter.ts
// 输出:你好,张三!今年25岁了。

tsconfig.json 会被自动读取,路径别名(paths)也自动生效。Bun 会尊重你已有的 tsconfig 配置,不需要额外适配。

JSX 支持

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// app.tsx
import React from "react";

interface CardProps {
  title: string;
  children: React.ReactNode;
}

const Card = ({ title, children }: CardProps) => {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </div>
  );
};

const App = () => (
  <Card title="欢迎使用 Bun">
    <p>BunTypeScriptJSX 开发变得无比简单!</p>
  </Card>
);

export default App;
1
bun run app.tsx  # 直接运行,不需要任何配置!

Bun 内置了 esbuild 来处理 JSX/TSX 编译,速度比 tsc 快 10-20 倍。当然了,最终上线前还是建议用 tsc 做完整类型检查。


5.5 环境变量

Bun 自动加载 .env 文件,不需要 dotenv 包——又一个"不需要安装额外依赖"的例子。

.env 文件

1
2
3
4
# .env
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=hello-world-secret
NODE_ENV=development

代码中使用

1
2
3
4
console.log(process.env.DATABASE_URL);
// postgres://localhost:5432/myapp
console.log(process.env.API_KEY);
// hello-world-secret

多环境配置

Bun 按以下顺序加载 .env 文件,后面的覆盖前面的:

.env                        # 所有环境共享的基础配置
.env.local                  # 本地覆盖(不提交到 Git,优先级最高)
.env.[BUN_ENV]              # 按环境加载,比如 .env.development 或 .env.production

如果设置了 BUN_ENV=development,会额外加载 .env.development。不设置时默认读取 .env.env.local


5.6 HTTP 服务开发

这是 Bun 最闪耀的部分之一——不用 Express,不用 Fastify,原生 API 写 HTTP 服务,性能比 Node 快 2-3 倍。

Bun.serve 基础

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// server.ts
const server = Bun.serve({
  port: 3000,          // 端口号(也可以是字符串如 "3000")
  hostname: "0.0.0.0", // 监听所有网卡,默认 localhost
  fetch(req) {
    return new Response("Hello from Bun HTTP server!");
  },
});

console.log(`🚀 服务启动啦:http://${server.hostname}:${server.port}`);
1
2
bun run server.ts
# 服务启动:bun serving on http://localhost:3000

注意:port 如果被占用,Bun 会自动选择下一个可用端口,并通过 server.port 获取实际端口号。写死端口时记得做好错误处理。

JSON 响应

1
2
3
4
5
6
7
8
9
async fetch(req) {
  const data = {
    message: "Hello JSON!",
    timestamp: new Date().toISOString(),
    count: 42,
  };
  // Bun 专用快捷方式,自动设置 Content-Type: application/json
  return Response.json(data);
}

注意:Response.json() 是 Bun/Fetch API 的标准方法,Node.js 12+ 也支持。放心用。

路由处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
async fetch(req) {
  const url = new URL(req.url);
  const pathname = url.pathname;

  // GET /api/users - 返回用户列表
  if (pathname === "/api/users" && req.method === "GET") {
    return Response.json([
      { id: 1, name: "张三" },
      { id: 2, name: "李四" },
    ]);
  }

  // GET /api/users/:id - 返回单个用户
  if (pathname.startsWith("/api/users/") && req.method === "GET") {
    const id = pathname.split("/")[3];
    return Response.json({ id, name: `用户${id}` });
  }

  return new Response("Not Found", { status: 404 });
}

更复杂的路由(如 RESTful 风格参数解析)可以引入 bun:router,或使用社区路由库如 @bunjs/router

静态文件服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 注意:serve 是 Bun.serve,不是从 "bun" 导入的
const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    const filePath = url.pathname === "/"
      ? "./public/index.html"
      : `./public${url.pathname}`;

    const file = Bun.file(filePath);
    // BunFile 对象可以直接作为 Response 的 body
    return new Response(file);
  },
});

Bun.file() 返回一个 BunFile 对象,Bun 会自动读取文件内容并确定 MIME 类型。BunFile 是惰性加载的,大文件也不会撑爆内存。

WebSocket 服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const server = Bun.serve({
  port: 3000,
  fetch(req, server) {
    // 尝试升级为 WebSocket
    const success = server.upgrade(req);
    if (success) return;        // 升级成功,不返回 Response
    return new Response("Upgrade Required", { status: 426 });
  },
  websocket: {
    open(ws) {
      console.log("🟢 新连接:", ws.remoteAddress);
      ws.send("欢迎连接!");
    },
    message(ws, msg) {
      console.log("📨 收到消息:", msg);
      ws.send(`你说了: ${msg}`);  // echo 回去
    },
    close(ws, code, reason) {
      console.log("🔴 连接关闭:", code, reason);
    },
    // 可选:ping/pong 心跳
    ping(ws, data) {},
    pong(ws, data) {},
  },
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
async fetch(req) {
  // 读取 Cookie
  const cookie = req.headers.get("Cookie") || "";
  const sessionId = cookie
    .split("; ")
    .find(c => c.startsWith("session="))
    ?.split("=")[1];

  const body = JSON.stringify({ sessionId: sessionId ?? null });
  const response = new Response(body, {
    headers: { "Content-Type": "application/json" },
  });

  // 设置 Cookie
  response.headers.append(
    "Set-Cookie",
    "session=abc123; Path=/; HttpOnly; Max-Age=3600"
  );

  return response;
}

5.7 测试

Bun 内置的测试运行器速度极快(比 Jest 快 10-30 倍),而且兼容 Jest 大部分 API——你不需要重写现有的测试。

写一个测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// math.test.ts
import { test, expect, describe } from "bun:test";

describe("数学运算", () => {
  test("加法", () => {
    expect(1 + 1).toBe(2);
  });

  test("乘法", () => {
    expect(3 * 4).toBe(12);
  });

  test("异步操作", async () => {
    const result = await Promise.resolve(42);
    expect(result).toBe(42);
  });
});
1
2
3
4
5
6
7
8
bun test
# bun test v1.3.x
#   math.test.ts
#    数学运算
#      ✓ 加法 (1ms)
#      ✓ 乘法
#      ✓ 异步操作
#  3 pass 0 fail 3 tests

如果你写过 Jest 测试,上面的代码应该看起来非常眼熟——Bun 的目标就是让 Jest 用户无缝切换。

Mock 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { test, expect, mock } from "bun:test";

test("Mock 函数演示", async () => {
  // 创建一个返回固定值的 mock 函数
  const fn = mock(() => 42);
  console.log(fn()); // 42

  // 创建一个异步 mock 函数
  const fetchUser = mock(async (id: number) => ({
    id,
    name: `User ${id}`,
  }));

  const user = await fetchUser(1);
  console.log(user); // { id: 1, name: "User 1" }
  expect(fetchUser).toHaveBeenCalledWith(1);
});

mock() 是 Bun 自己的实现,不需要安装 jest-mocksinon

并发测试

1
2
3
test("并发测试", async () => {
  // Bun 会尽可能并行运行标记了 concurrent: true 的测试
}, { concurrent: true });

并发测试要小心共享状态——多个测试同时修改全局变量会让你debug到怀疑人生。

覆盖率报告

1
2
bun test --coverage
# 会在终端显示详细的覆盖率统计

指定测试文件

1
2
bun test ./src/math.test.ts           # 只运行特定文件
bun test --test-name-pattern "加法"   # 只运行名字含"加法"的测试

5.8 构建与打包

Bun 的构建工具适合轻量打包场景,比如类库、工具脚本。如果你要构建复杂的大型应用,Vite/Webpack 仍然是更好的选择。

基本打包

1
2
3
4
bun build ./src/index.tsx \
  --outdir=dist \
  --target=browser \
  --minify

多平台目标

1
2
3
4
5
6
7
8
# 浏览器(生成 ESM/CJS 模块)
bun build ./src/app.ts --target=browser --outdir=dist

# Node.js(生成 CJS 或 ESM)
bun build ./src/app.ts --target=node --outdir=dist

# Bun 运行时(生成纯 Bun 可执行文件)
bun build ./src/app.ts --target=bun --outdir=dist

单文件可执行文件(独立运行时)

1
2
3
# 打包为单文件可执行文件(需要 target=bun)
bun build --target=bun --outfile=myapp ./myapp.ts
./myapp  # 直接运行,不需要安装 Bun!

生成的可执行文件是平台相关的——Linux 上打包只能在 Linux 上运行,macOS同理。跨平台构建需要 Docker 或 CI。

环境变量内联

1
2
3
4
5
6
7
8
9
// app.ts
const dbUrl = process.env.DATABASE_URL ?? "localhost";

// build.ts
await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  env: "inline",  // process.env.* 在构建时被替换为实际值
});

注意:环境变量内联后,敏感信息(如 API Key)会直接写入产物,不要把包含密钥的代码提交到公开仓库。


5.9 代码质量工具

Bun 自带格式化工具,虽然生态不如 ESLint/Prettier 丰富,但对于中小项目已经够用。

bun fmt - 代码格式化

1
2
3
bun fmt                # 格式化当前目录所有文件
bun fmt ./src          # 格式化指定目录
bun fmt --check        # 只检查,不修改(适合 CI)

bun lint - 代码检查

1
bun lint               # 检查代码问题

Bun 的 lint 目前还在积极开发中,规则集不如 ESLint 丰富。有复杂 lint 需求的项目建议继续使用 ESLint。


5.10 SQLite 专项(bun:sqlite)

SQLite 是世界上最流行的数据库,Bun 内置了高性能 SQLite 驱动——比 better-sqlite3 快 3-6 倍,比 sql.js 快更多。而且不需要安装任何包,直接 import 就行。

基本操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { Database } from "bun:sqlite";

const db = new Database("app.db");  // 文件数据库,:memory: 是内存数据库

// 创建表
db.run(`
  CREATE TABLE IF NOT EXISTS posts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    content TEXT,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);

// 插入数据(使用参数化查询,防 SQL 注入)
const insert = db.query(
  "INSERT INTO posts (title, content) VALUES ($title, $content)"
);
insert.run({ $title: "我的第一篇文章", $content: "Hello Bun!" });
insert.run({ $title: "第二篇", $content: "SQLite 真好用!" });

// 查询所有
const all = db.query("SELECT * FROM posts").all();
console.log(all);
// 输出:
// [
//   { id: 1, title: "我的第一篇文章", content: "Hello Bun!", created_at: "2024-01-01 12:00:00" },
//   { id: 2, title: "第二篇", content: "SQLite 真好用!", created_at: "2024-01-01 12:01:00" }
// ]

// 条件查询(只取一条)
const one = db.query("SELECT * FROM posts WHERE id = $id").get({ $id: 1 });
console.log(one);
// { id: 1, title: "我的第一篇文章", content: "Hello Bun!", created_at: "..." }

// 更新数据
db.run("UPDATE posts SET title = $title WHERE id = $id", {
  $title: "更新后的标题",
  $id: 1,
});

// 删除数据
db.run("DELETE FROM posts WHERE id = $id", { $id: 2 });

db.query() 返回一个预编译语句对象,.all() 取所有行,.get() 取第一行,.run() 执行不返回结果集的操作。预编译语句可以复用,性能更好。

事务

1
2
3
4
5
6
7
8
9
db.run("BEGIN TRANSACTION");
try {
  db.run("INSERT INTO posts (title) VALUES ($t)", { $t: "标题A" });
  db.run("INSERT INTO posts (title) VALUES ($t)", { $t: "标题B" });
  db.run("COMMIT");
} catch (err) {
  db.run("ROLLBACK");
  console.error("事务失败,已回滚:", err);
}

Bun 的 SQLite 驱动也支持更便捷的 db.transaction() 包装:

1
2
3
4
5
6
const insertMany = db.transaction((titles: string[]) => {
  for (const title of titles) {
    db.run("INSERT INTO posts (title) VALUES ($t)", { $t: title });
  }
});
insertMany(["标题A", "标题B"]); // 自动开启事务,成功自动 COMMIT,失败自动 ROLLBACK

严格模式

1
2
3
4
5
6
7
8
9
// strict: true - 所有绑定参数必须带 $ 前缀,否则报错
const strictDb = new Database(":memory:", { strict: true });
try {
  // 注意:参数 key 是 "id",但查询占位符是 $id —— 少了 $ 前缀会报错
  strictDb.query("SELECT * FROM users WHERE id = $id").get({ id: 1 });
} catch (e) {
  console.log("报错:", e.message);
  // 输出类似:Parameter "id" must have a $ prefix (got "id")
}

严格模式可以帮助你尽早发现参数绑定错误,建议在生产环境中开启。


5.11 WebSocket 专项

Bun 原生支持 WebSocket,不需要安装任何 ws 库。和 HTTP 服务一样,都是 Bun.serve 的一部分。

Pub/Sub(发布/订阅)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Bun.serve({
  port: 3000,
  fetch(req, server) {
    const url = new URL(req.url);
    if (url.pathname === "/ws") {
      // 升级到 WebSocket,并携带额外数据
      server.upgrade(req, { data: { pathname: url.pathname } });
      return;
    }
    return new Response("Only WebSocket at /ws");
  },
  websocket: {
    open(ws) {
      ws.subscribe("general");              // 加入 "general" 频道
      console.log("🟢 用户加入 general 频道,当前地址:", ws.remoteAddress);
    },
    message(ws, msg) {
      // 注意:server.publish(频道, 消息) - 频道在前,消息在后
      server.publish("general", `广播: ${msg}`);
    },
    close(ws, code, reason) {
      ws.unsubscribe("general");            // 离开频道
      console.log("🔴 用户离开 general 频道", code, reason);
    },
  },
});

server.publish() 的参数顺序是 publish(channel, message),不是 publish(message, channel)——很多人第一次用会搞反。

配置选项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Bun.serve({
  port: 3000,
  fetch(req, server) { server.upgrade(req); return; },
  websocket: {
    // 每条消息最大 1MB(默认 16MB)
    maxPayloadLength: 1024 * 1024,
    // 空闲超时 60 秒后自动关闭(默认 120 秒)
    idleTimeout: 60,
    // 启用 per-message 压缩(默认 false)
    perMessageDeflate: true,
    // 每条 incoming 消息最大体积(默认 16MB)
    maxFragmentSize: 65536,
  },
});

本章小结

本章是 Bun 的"实战指南",覆盖了从安装到使用的完整流程。总结一下核心知识点:

安装:Windows 一行命令、macOS 用 Homebrew、Linux 用 curl、Docker 多镜像可选。bun upgrade 一键升级。

包管理bun install/add/remove/update,与 npm 用法完全一致,零迁移成本。

运行脚本bun run 替代 nodenpxbunx 替代 npx 不安装运行。

TypeScript 支持:零配置,.ts/.tsx 直接跑,Bun 自动处理类型检查和编译(但上线前仍建议用 tsc 做完整检查)。

HTTP 服务Bun.serve 写起来比 Express 简洁,性能高 2-3 倍(Express 场景可达 3 倍),原生支持 WebSocket。

测试bun test 兼容 Jest API,速度快 10-30 倍,内置 Mock 和覆盖率报告。

构建bun build 适合轻量打包,--target=bun --outfile 生成单文件可执行文件。

SQLitebun:sqlite 内置驱动,参数化查询防注入,比 better-sqlite3 快 3-6 倍。

WebSocketserver.upgrade() 升级连接,ws.subscribe/publish 做 Pub/Sub 广播。

代码质量bun fmt + bun lint,开箱即用,虽然生态不如 ESLint/Prettier 丰富,但对简单项目足够。

学完这一章,你应该已经可以:

  • 用 Bun 跑 TypeScript 代码
  • 写一个带路由和 JSON 接口的 HTTP 服务
  • 用 Bun 原生测试框架写测试
  • 用 SQLite 存储数据
  • 打包一个可执行文件

下一章我们来聊聊 Bun 的生态和插件系统,以及如何参与贡献。🎉

最后修改 March 29, 2026: 新增 bun 教程 (8f450cb)