编程 Bun 1.x 深度实战:当 Zig 遇上 JavaScriptCore——从底层架构到 SIMD 性能优化、全栈工具链整合与生产级迁移的完整指南(2026)

2026-06-18 00:23:59 +0800 CST views 8

Bun 1.x 深度实战:当 Zig 遇上 JavaScriptCore——从底层架构到 SIMD 性能优化、全栈工具链整合与生产级迁移的完整指南(2026)

当你第 N 次等待 npm install 完成时,当你发现 Node.js 冷启动时间占了 Lambda 函数总耗时的 60% 时,当你为了配置 TypeScript 转译不得不维护一堆 tsconfig.json + babel.config.js + webpack.config.js 时——Bun 来了,带着 Zig 的极致性能和 JavaScriptCore 的优雅设计,重新定义了什么是「JavaScript 工具链」。

前言:为什么你需要关心 Bun

2026 年初,Bun 在不到一个月的时间里连续推出四个重要版本(v1.3.6 到 v1.3.9),这不是简单的版本迭代,而是一次系统性的技术宣言:JavaScript 工具链的理想形态,不应该是一堆散乱工具的拼凑,而应该是一个高度集成、性能极致、体验统一的全栈运行时

本文将深入探讨:

  • Bun 的底层架构:为什么选择 Zig + JavaScriptCore 这个组合?
  • 性能优化的系统方法论:SIMD 指令集、Mimalloc v3、FastStringifier 背后的原理
  • 全栈工具链的「电池内置」哲学:从 Bun.Archivebun markdown 的工程权衡
  • 生产级迁移实战:从 Node.js 迁移到 Bun 的真实路径、坑点与解决方案
  • 与 Deno、Node.js 的深度对比:不只是基准测试,更是架构哲学的分歧

第一章:Bun 的诞生背景与核心设计哲学

1.1 JavaScript 工具链的「碎片化危机」

如果你是一个 2026 年的 JavaScript 开发者,启动一个新项目意味着什么?

# 一个「标准」的现代化 JavaScript 项目需要这些工具
node.js          # 运行时
npm / yarn / pnpm # 包管理器
typescript       # 类型检查
babel / swc      # 转译
webpack / vite / esbuild / rolldown # 打包
jest / vitest / mocha # 测试
prettier / eslint # 代码格式化与检查
tsup / tsc       # 类型打包
dotenv           # 环境变量
cross-env        # 跨平台环境变量
nodemon / tsx    # 开发热重载

这不是工具丰富,这是工具链碎片化。每个工具都有自己的配置格式、自己的插件生态、自己的性能瓶颈。当你的 package.jsondevDependencies 有 47 个包,而你只是想写一个简单的 HTTP API 时,出了问题你甚至不知道该去查哪个工具的文档。

Bun 的创始人 Jarred Sumner 在创建 Bun 之前,正是被这个问题折磨得够呛。他的核心洞察是:

这些工具不应该是一堆独立的可执行文件,而应该是一个统一运行时的一部分。

这就像是说:你买一台 MacBook,不需要分别买屏幕、键盘、触控板、操作系统、电池管理系统,然后自己组装——这些东西本来就应该是一个整体。

1.2 「All-in-One」不是捆绑,是架构选择

很多人误解 Bun 的「全家桶」定位,认为它只是把现有工具打包在一起。这是完全错误的。

Bun 的「All-in-One」是从底层架构开始的统一设计

传统 JavaScript 工具链(碎片化):
Node.js (V8) → 启动慢、内存占用高
  + npm (Node.js 编写) → 安装慢
  + TypeScript (Node.js 编写) → 转译慢
  + webpack (Node.js 编写) → 打包慢
  + jest (Node.js 编写) → 测试慢
  = 整个工具链都受限于 Node.js 的性能天花板

Bun(统一架构):
Zig (系统级性能) 
  + JavaScriptCore (高效 JS 引擎)
  + 统一的内存管理 (Mimalloc v3)
  + 统一的事件循环
  + 原生的 TypeScript 转译 (基于 Safari 的 JS 转译器)
  = 每个环节都针对性能优化,且共享同一套基础设施

关键区别:Bun 不是「用 Node.js 写的包管理器」,而是「用 Zig 写的、直接操作系统调用的原生可执行文件」。这意味着 bun install 的性能瓶颈不是 Node.js 的 I/O 模型,而是操作系统的文件系统性能上限。

1.3 为什么是 Zig?为什么是 JavaScriptCore?

这两个选择是 Bun 性能优势的基石,也是理解 Bun 架构的关键。

Zig:系统级编程语言的新选择

Zig 是一门相对年轻的系统级编程语言,设计目标是「替代 C 语言在系统编程中的地位」。它的核心特性包括:

  • 手动内存管理,但没有 C 的陷阱:Zig 要求你显式处理内存,但通过 defer 关键字和所有权模型,让内存管理变得可预测
  • 与 C 的互操作性:可以直接调用 C 函数,也可以被 C 调用,无需 FFI 开销
  • 编译时执行:Zig 的 comptime 机制允许在编译期执行代码,生成高度优化的机器码
  • 无隐藏控制流:Zig 代码中,你能看到的每一行代码,就是它实际执行的逻辑——没有构造函数、没有隐式类型转换、没有运算符重载

Bun 选择 Zig 的核心原因:性能可预测性

当你用 Zig 写一个 HTTP 解析器时,你知道每一行代码会生成什么样的机器指令。当你用 TypeScript 写时,你不知道 V8 的 JIT 编译器会对你的代码做什么——它可能优化,也可能反优化(deoptimization),这种不确定性在生产环境中是致命的。

// Zig 代码示例:高效的缓冲区复制
fn fastCopy(dest: []u8, src: []const u8) void {
    @memcpy(dest.ptr, src.ptr, src.len);
}

// 这段代码的性能:
// 1. 编译期会生成最优的 SIMD 指令(如果目标 CPU 支持)
// 2. 没有函数调用开销(内联)
// 3. 没有边界检查开销(@memcpy 是内置函数)

对比一下,如果你用 TypeScript 写一个缓冲区复制:

// TypeScript 代码:同样的缓冲区复制
function fastCopy(dest: Buffer, src: Buffer): void {
  src.copy(dest);
}

// 实际执行时的性能问题:
// 1. V8 需要动态判断 src 和 dest 的类型
// 2. 可能需要装箱/拆箱操作
// 3. 边界检查是运行时进行的
// 4. JIT 编译器可能随时对这个函数进行反优化

JavaScriptCore:被低估的性能怪兽

大多数 JavaScript 开发者熟悉 V8(Chrome 和 Node.js 的引擎),但对 JavaScriptCore(Safari 的引擎)了解不多。实际上,JSC 在某些场景下比 V8 更快,特别是:

  • 启动速度:JSC 的字节码解释器设计更加精简,冷启动时间显著低于 V8
  • 内存占用:JSC 的对象模型和垃圾回收器对内存的使用更加保守
  • 并发 GC:JSC 的垃圾回收器可以更好地利用多核 CPU

Bun 选择 JSC 而不是 V8 的核心原因:启动速度和内存占用的优势,在 Serverless 和微服务等场景下是决定性的

一个具体的数字:同样的 Hello World 程序,Node.js(V8)的冷启动时间约为 300ms,而 Bun(JSC)的冷启动时间约为 80ms——快了近 4 倍

在 AWS Lambda 这样的 Serverless 环境中,这个差异意味着:

  • Node.js 函数:冷启动 300ms + 业务逻辑 200ms = 总耗时 500ms
  • Bun 函数:冷启动 80ms + 业务逻辑 200ms = 总耗时 280ms

对于对延迟敏感的应用,这 220ms 的差距就是用户体验的天壤之别。


第二章:Bun 的架构深度解析

2.1 整体架构概览

Bun 的架构可以分为四层:

┌─────────────────────────────────────────────────────┐
│         开发者 API 层                               │
│  Bun.serve() / Bun.file() / Bun.sql() / ...       │
└─────────────────────────────────────────────────────┘
                         ▲
                         │
┌─────────────────────────────────────────────────────┐
│         统一工具链层                                 │
│  包管理器 / 测试框架 / 打包器 / 转译器 / ...        │
└─────────────────────────────────────────────────────┘
                         ▲
                         │
┌─────────────────────────────────────────────────────┐
│         运行时核心层                                 │
│  JavaScriptCore 引擎 / Zig 原生模块 / 事件循环     │
└─────────────────────────────────────────────────────┘
                         ▲
                         │
┌─────────────────────────────────────────────────────┐
│         系统接口层                                   │
│  POSIX API / Windows API / Libuv 替代              │
└─────────────────────────────────────────────────────┘

关键设计决策:Bun 没有使用 libuv(Node.js 使用的跨平台异步 I/O 库),而是直接用 Zig 实现了事件循环。这意味着 Bun 的事件循环是针对 JavaScriptCore 的垃圾回收器和执行模型优化的,而不是一个通用的解决方案。

2.2 JavaScriptCore 集成:不只是嵌入引擎

Bun 集成 JSC 的方式非常深入,不是简单的「嵌入一个 JS 引擎」,而是将 JSC 的垃圾回收器与 Bun 的事件循环深度整合

垃圾回收与事件循环的协同

JSC 使用的是分代垃圾回收器(Generational GC),将对象分为「新生代」(短期对象)和「老生代」(长期对象)。GC 会在以下时机触发:

  • 新生代满了 → Minor GC(快速,stop-the-world)
  • 老生代满了 → Major GC(慢速,stop-the-world)

在 Node.js 中,GC 的触发时机与事件循环是解耦的——V8 的 GC 不知道事件循环在做什么,这可能导致:

  1. 在一次 HTTP 请求的中间触发 Major GC → 所有请求都卡住 100ms+
  2. 事件循环的某个阶段(比如 poll 阶段)被 GC 打断

Bun 的解决方案是:让 JSC 的 GC 感知事件循环的状态

// Bun 的内部实现概念(简化版)
class EventLoop {
  private gcIntegration: JSCGCIntegration;
  
  run() {
    while (this.hasTasks()) {
      this.processTimers();       // setTimeout / setInterval
      this.processPendingCallbacks(); // Promise callbacks
      
      // 关键:在事件循环的「空闲」阶段(介于两个 tick 之间)触发 GC
      if (this.isIdle() && this.gcIntegration.shouldCollect()) {
        this.gcIntegration.collect(); // 触发 GC
      }
      
      this.processIO();          // 网络 I/O、文件 I/O
      this.processImmediate();   // setImmediate
    }
  }
}

这种设计的实际效果是:GC 暂停几乎不会对请求延迟产生影响,因为 GC 总是在「没有请求正在处理」的时刻触发。

根据 Bun 团队的基准测试,这种集成让空闲 CPU 时间减少 100 倍,空闲内存使用量减少 40%

2.3 Zig 原生模块:性能的关键路径

Bun 中性能最关键的模块都是用 Zig 直接编写的,包括:

HTTP 解析器

// Bun 的 HTTP 解析器(概念代码)
pub fn parseRequest(buffer: []const u8) !HttpRequest {
    var parser = HttpParser.init();
    
    // 使用 SIMD 指令加速头部字段查找
    const method_end = try simd.findFirst(buffer, ' ');
    const path_end = try simd.findFirst(buffer[method_end..], ' ');
    
    // 零拷贝头部解析
    var headers = try parseHeadersSIMD(buffer[path_end..]);
    
    return HttpRequest{
        .method = buffer[0..method_end],
        .path = buffer[method_end+1..path_end],
        .headers = headers,
        .body = try parseBody(buffer),
    };
}

为什么 Zig 写的 HTTP 解析器比 Node.js 的快?

Node.js 的 HTTP 解析器(llhttp)是用 C 写的,已经很快了。但 Bun 的优势在于:

  1. 与运行时的深度集成:解析完的 HTTP 请求可以直接映射为 JSC 的内部字符串表示,无需复制
  2. SIMD 加速:对常见的头部字段(Content-Type、Authorization 等)使用 SIMD 指令并行匹配
  3. 内存池:使用 Mimalloc 的内存池,避免频繁向操作系统申请/释放内存

文件系统操作

// Bun 的 fs.readFile 实现(概念代码)
pub fn readFile(path: []const u8) ![]u8 {
    // 使用 POSIX 的 open/read 而不是直接用 C 标准库的 fopen
    const fd = try posix.open(path, posix.O.RDONLY, 0);
    defer posix.close(fd);
    
    // 使用 stat 获取文件大小,一次性分配缓冲区
    const stat = try posix.fstat(fd);
    const buffer = try mimalloc.alloc(u8, stat.size);
    
    // 使用 pread 避免 lseek + read 的系统调用开销
    var bytes_read: usize = 0;
    while (bytes_read < stat.size) {
        const n = try posix.pread(fd, buffer[bytes_read..], bytes_read);
        bytes_read += n;
    }
    
    return buffer;
}

对比 Node.js 的 fs.readFile

  • Node.js 使用 libuv 的异步 I/O,这引入了额外的开销(事件循环回调、Buffer 拷贝)
  • Bun 在 Zig 层直接处理,对于小文件(< 1MB)使用同步 I/O 反而更快(减少了回调开销)

2.4 内置工具链的架构统一性

Bun 的「电池内置」哲学不仅是功能整合,更是架构层面的统一

包管理器:不只是快,是架构先进

bun installnpm install 快 25 倍,原因不只是「用 Zig 写的」,更是架构设计的不同:

npm 的安装流程:
1. 解析 package.json → 生成依赖树(Node.js 执行)
2. 检查 node_modules → 对比依赖版本(Node.js 执行)
3. 下载 tarball → 解压到 node_modules(Node.js + libuv)
4. 执行生命周期脚本(preinstall / postinstall)→ 启动子进程(Node.js)
= 每一步都受限于 Node.js 的性能

bun install 的流程:
1. 解析 package.json → 生成依赖树(Zig 执行,使用手写解析器)
2. 检查本地缓存 → 使用内容寻址存储(CAS),避免重复下载
3. 并行下载 + 解压 → 使用 Zig 的异步 I/O,充分利用带宽
4. 写入 node_modules → 使用硬链接(hard link)而不是复制文件
= 每一步都针对性能优化

硬链接的妙处:当你有多个项目都依赖 react@18.2.0 时,npm 会在每个项目的 node_modules 里复制一份 React——如果有 10 个项目,就有 10 份 React 的拷贝。

Bun 的做法是:在全局缓存里存储一份 react@18.2.0,然后在每个项目的 node_modules 里创建硬链接指向这份缓存。这样:

  • 磁盘占用减少 90%+
  • 安装时间减少(不需要复制文件)
  • 更新依赖时,只需要更新缓存里的那份

测试框架:原生集成带来的体验飞跃

Bun 的测试框架(bun test)不是像 Jest 那样「用 Node.js 写的测试工具」,而是直接集成在运行时内部

这意味着:

  1. 测试发现是并行的:Bun 启动时,就在后台并行扫描项目里的所有 *.test.ts 文件
  2. 测试执行是隔离的:每个测试文件运行在独立的 JavaScript 上下文中,避免测试间的状态污染
  3. Mock 是原生支持的Bun.test.mock 直接操作 JSC 的内部函数,不需要像 Jest 那样使用复杂的 Mock 机制
// Bun 的测试代码示例
import { test, expect, mock } from "bun";

// 原生 Mock —— 直接替换函数的实现
const originalFetch = globalThis.fetch;
globalThis.fetch = mock(async (url: string) => {
  return new Response(JSON.stringify({ mocked: true }));
});

test("API 测试", async () => {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();
  expect(data.mocked).toBe(true);
});

// 恢复原始实现
globalThis.fetch = originalFetch;

对比 Jest 的 Mock:

// Jest 的 Mock —— 需要复杂的配置
jest.mock("node-fetch"); // 需要 mock 整个模块
import fetch from "node-fetch";
(fetch as jest.Mock).mockResolvedValue(new Response(...));

// 问题:
// 1. 需要额外的 npm 包(node-fetch 或 whatwg-fetch)
// 2. Mock 是基于模块系统的,不够灵活
// 3. TypeScript 类型推导会丢失

第三章:Bun 的性能优化深度解析

3.1 SIMD 指令集:Bun 的性能护城河

SIMD(Single Instruction, Multiple Data)是现代 CPU 的「隐藏武器」——一条指令可以同时处理多个数据。Bun 是 JavaScript 生态中第一个系统性使用 SIMD 指令的项目。

SIMD 基础:从概念到实战

假设你要实现一个函数:计算一个字符串里有多少个 @ 字符。

传统做法(标量)

function countAtSigns_scalar(str: string): number {
  let count = 0;
  for (let i = 0; i < str.length; i++) {
    if (str[i] === '@') count++;
  }
  return count;
}

这段代码每次循环处理一个字符,在现代 CPU 上,这是极大的浪费——CPU 每次可以从内存加载 64 字节(一个缓存行),但我们只用了其中 1 个字节。

SIMD 做法

// Zig + SIMD(概念代码)
fn countAtSignsSIMD(buffer: []const u8) usize {
    const simd_width = 16; // SSE4.2 可以一次处理 16 字节
    var count: usize = 0;
    
    // 每次处理 16 个字节
    var i: usize = 0;
    while (i + simd_width <= buffer.len) {
        const chunk = @as([16]u8, buffer[i..i+16]);
        const matches = @simd.compare(chunk, '@'); // SIMD 比较
        count += @popcount(matches); // 统计匹配数
        i += simd_width;
    }
    
    // 处理剩余部分(标量)
    while (i < buffer.len) {
        if (buffer[i] == '@') count++;
        i += 1;
    }
    
    return count;
}

性能差异:SIMD 版本可以比标量版本快 10-16 倍(取决于 CPU 支持的 SIMD 宽度)。

Bun 中 SIMD 的实际应用

Bun 在以下场景中系统性地使用 SIMD:

  1. Buffer.indexOf:在二进制数据中查找特定模式,使用 SIMD 加速后,性能提升 2 倍
  2. RegExp 前缀匹配:对于正则表达式的前缀部分(比如 /^https?:\/\//),使用 SIMD 并行匹配多个可能的字符,性能提升 3.9 倍
  3. CRC32 计算:用于检测数据完整性,使用 SIMD 的 PCLMULQDQ 指令(Intel 的 SIMD 扩展),性能提升 20 倍
  4. Markdown 渲染:解析 Markdown 文档时,需要大量字符串匹配(比如识别 **bold**[link](url)),使用 SIMD 加速后,渲染速度提升显著
Bun 的 SIMD 优化不是「可选功能」,而是「默认启用」。
这意味着:只要你在一个支持 SIMD 的 CPU 上运行 Bun(几乎所有可能的生产环境),你就在自动享受这些优化。

3.2 Mimalloc v3:为多线程时代重新设计内存分配

Mimalloc(Microsoft 开发的高性能内存分配器)是 Bun 内存管理的基础。Bun 在 2026 年初升级到 Mimalloc v3,这不是一个简单的版本升级,而是对多线程 JavaScript 应用特征的深刻理解。

为什么内存分配器对 JavaScript 性能至关重要?

JavaScript 是「高度分配」的语言——几乎每个操作都会触发内存分配:

  • 创建一个对象 → 分配内存
  • 拼接字符串 → 分配内存
  • 处理 HTTP 请求 → 分配内存(请求对象、响应对象、Buffer...)

如果一个内存分配器效率低下,JavaScript 应用的性能就会直接受损。

Mimalloc 的核心优势

传统 malloc(比如 glibc 的 ptmalloc):
- 多线程环境下,使用全局锁 → 线程越多,竞争越激烈
- 内存碎片率高 → 长期运行后,内存占用持续增长
- 对小对象(< 256 字节)的分配效率低

Mimalloc:
- 每个线程有独立的「内存池」→ 无锁分配(大部分时候)
- 使用「页面映射」技术 → 内存碎片率极低
- 对小对象使用「自由列表」→ 分配/释放都是 O(1)

Bun 集成 Mimalloc 的方式也非常讲究:不是简单地链接 Mimalloc 库,而是让 JSC 的垃圾回收器直接使用 Mimalloc 分配内存

这意味着:

  • JavaScript 对象的分配 → Mimalloc
  • Zig 原生模块的内存分配 → Mimalloc
  • 整个 Bun 运行时的所有内存分配 → 统一的 Mimalloc 实例

效果:在并发请求处理场景下(比如一个 HTTP 服务器同时处理 1000 个请求),Bun 的内存分配延迟比 Node.js 低 40%,内存占用低 30%。

3.3 FastStringifier:隐藏的性能杀手

「把对象转换成 JSON 字符串」——这个操作在 Web 开发中无处不在:

  • HTTP API 返回 JSON
  • 缓存数据到 Redis
  • 记录日志

大多数 JavaScript 开发者不会注意到这个操作的性能开销,但在高并发场景下,它可能成为系统的瓶颈。

JSON.stringify 的性能问题

JSON.stringify 的性能瓶颈在于:

  1. 类型检查开销:对于每个要序列化的属性,引擎都需要检查它的类型(字符串?数字?对象?循环引用?)
  2. 字符串拼接开销:JavaScript 的字符串是不可变的,每次拼接都会创建新的字符串
  3. 递归开销:嵌套对象的序列化需要递归,这可能导致栈溢出

Bun 的解决方案:FastStringifier

Bun 引入了 Response.json() 方法,它内部使用 FastStringifier(一个高度优化的 JSON 序列化器)。

// 传统方式:使用 JSON.stringify
app.get("/api/users", () => {
  const users = db.query("SELECT * FROM users");
  return new Response(JSON.stringify(users));
});

// Bun 方式:使用 Response.json()
app.get("/api/users", () => {
  const users = db.query("SELECT * FROM users");
  return Response.json(users); // 内部使用 FastStringifier
});

性能差异Response.json()new Response(JSON.stringify(...)) 快 3.5 倍

FastStringifier 的优化原理:

  1. 预计算对象形状:如果你的 API 总是返回同样结构的对象({id, name, email}),FastStringifier 会「记住」这个形状,下次直接生成对应的 JSON 字符串,跳过类型检查
  2. 零拷贝字符串拼接:使用「字符串块链」技术,避免中间字符串的创建
  3. SIMD 加速转义:对于字符串中的特殊字符(", \, \n 等),使用 SIMD 指令并行查找和转义

第四章:Bun 全栈工具链实战

4.1 从零开始:用 Bun 构建一个完整的 Web API

理论讲了很多,现在让我们动手写一个真实的项目。我们的目标是:

  • 一个 RESTful API 服务器(支持 CRUD)
  • 连接 PostgreSQL 数据库
  • 使用 Redis 做缓存
  • 编写测试
  • 打包为单文件可执行程序

步骤 1:项目初始化

# 安装 Bun(如果还没安装)
curl -fsSL https://bun.sh/install | bash

# 初始化项目
bun init

bun init 会创建一个最小化的项目结构:

my-bun-api/
├── package.json       # Bun 兼容 npm 的 package.json
├── tsconfig.json      # TypeScript 配置(Bun 原生支持,无需配置)
├── src/
│   └── index.ts      # 入口文件
└── bun.lockb         # Bun 的锁文件(比 package-lock.json 快得多)

注意:不同于 Node.js 项目,Bun 项目不需要 .babelrcwebpack.config.js 等配置文件——TypeScript 是原生支持的。

步骤 2:编写 HTTP 服务器

// src/index.ts
import { Database } from "bun:sqlite"; // Bun 内置 SQLite 支持

// 定义数据类型
interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

// 初始化数据库
const db = new Database("app.db");
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

// 创建 HTTP 服务器
const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    const path = url.pathname;
    const method = req.method;

    // CORS 处理(Bun 原生支持)
    if (method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
          "Access-Control-Allow-Headers": "Content-Type",
        },
      });
    }

    // 路由处理
    if (path === "/api/users" && method === "GET") {
      // 获取所有用户
      const users = db.query("SELECT * FROM users").all() as User[];
      return Response.json(users); // FastStringifier!
    }

    if (path === "/api/users" && method === "POST") {
      // 创建新用户
      const body = await req.json() as Pick<User, "name" | "email">;
      
      // 输入验证(Bun 内置验证工具)
      if (!body.name || !body.email) {
        return Response.json(
          { error: "Name and email are required" },
          { status: 400 }
        );
      }

      try {
        const result = db
          .query("INSERT INTO users (name, email) VALUES (?, ?)")
          .run(body.name, body.email);

        return Response.json(
          { id: result.lastInsertRowid, ...body },
          { status: 201 }
        );
      } catch (err: any) {
        if (err.message.includes("UNIQUE constraint")) {
          return Response.json(
            { error: "Email already exists" },
            { status: 409 }
          );
        }
        throw err;
      }
    }

    // 动态路由:/api/users/:id
    const userMatch = path.match(/^\/api\/users\/(\d+)$/);
    if (userMatch && method === "GET") {
      const userId = parseInt(userMatch[1]);
      const user = db
        .query("SELECT * FROM users WHERE id = ?")
        .get(userId) as User | undefined;

      if (!user) {
        return Response.json({ error: "User not found" }, { status: 404 });
      }
      return Response.json(user);
    }

    if (userMatch && method === "PUT") {
      const userId = parseInt(userMatch[1]);
      const body = await req.json() as Partial<Pick<User, "name" | "email">;

      const result = db
        .query("UPDATE users SET name = COALESCE(?, name), email = COALESCE(?, email) WHERE id = ?")
        .run(body.name, body.email, userId);

      if (result.changes === 0) {
        return Response.json({ error: "User not found" }, { status: 404 });
      }

      return Response.json({ message: "User updated successfully" });
    }

    if (userMatch && method === "DELETE") {
      const userId = parseInt(userMatch[1]);
      const result = db.query("DELETE FROM users WHERE id = ?").run(userId);

      if (result.changes === 0) {
        return Response.json({ error: "User not found" }, { status: 404 });
      }

      return new Response(null, { status: 204 });
    }

    // 404
    return Response.json({ error: "Not found" }, { status: 404 });
  },

  // 错误处理
  error(error) {
    console.error("Server error:", error);
    return Response.json(
      { error: "Internal server error" },
      { status: 500 }
    );
  },
});

console.log(`🚀 Server running at http://localhost:${server.port}`);

关键亮点

  1. 原生 TypeScript 支持:直接运行 .ts 文件,无需转译
  2. 内置 SQLitebun:sqlite 模块提供高性能的 SQLite 访问(比 better-sqlite3 快 3 倍)
  3. Response.json():使用 FastStringifier,性能卓越
  4. 优雅的错误处理error 回调统一处理未捕获异常

步骤 3:添加 PostgreSQL 支持

虽然 Bun 内置了 SQLite,但生产环境通常使用 PostgreSQL。Bun 提供了原生的 PostgreSQL 客户端(不需要 pg 包)。

// src/db.ts
import { sql } from "bun";

// Bun 的原生 SQL 客户端(支持 PostgreSQL、MySQL、SQLite)
const db = sql({
  url: process.env.DATABASE_URL || "postgresql://localhost:5432/myapp",
});

// 类型安全的查询
export async function getUsers(): Promise<User[]> {
  return await db`
    SELECT * FROM users
    ORDER BY created_at DESC
  `;
}

export async function createUser(name: string, email: string): Promise<User> {
  const [user] = await db`
    INSERT INTO users (name, email)
    VALUES (${name}, ${email})
    RETURNING *
  `;
  return user;
}

// 事务支持
export async function transferFunds(fromId: number, toId: number, amount: number) {
  return await db.transaction(async (tx) => {
    const [fromUser] = await tx`
      SELECT balance FROM users WHERE id = ${fromId} FOR UPDATE
    `;
    
    if (fromUser.balance < amount) {
      throw new Error("Insufficient funds");
    }

    await tx`
      UPDATE users SET balance = balance - ${amount} WHERE id = ${fromId}
    `;
    await tx`
      UPDATE users SET balance = balance + ${amount} WHERE id = ${toId}
    `;
  });
}

Bun 的 SQL 客户端的优势

  1. 标签模板字面量sql 函数使用标签模板(tagged template literals),自动处理参数转义,防止 SQL 注入
  2. 类型推导:Bun 可以根据 SQL 查询的结构,自动推导返回类型
  3. 连接池:内置连接池管理,无需额外配置
  4. 性能:比 pg 包快 2 倍(因为是用 Zig 写的,减少了 Node.js 层的开销)

4.2 测试:原生测试框架的威力

Bun 的测试框架是内置的,不需要安装 Jest、Vitest 等额外依赖。

// src/index.test.ts
import { test, expect, beforeAll, afterAll } from "bun:test";
import { Database } from "bun:sqlite";
import { getUsers, createUser } from "./db";

// 使用内存数据库进行测试
let db: Database;

beforeAll(() => {
  db = new Database(":memory:");
  db.run(`
    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL
    )
  `);
});

afterAll(() => {
  db.close();
});

test("创建用户", async () => {
  const user = await createUser("Alice", "alice@example.com");
  expect(user.name).toBe("Alice");
  expect(user.email).toBe("alice@example.com");
  expect(user.id).toBeDefined();
});

test("获取用户列表", async () => {
  await createUser("Bob", "bob@example.com");
  const users = await getUsers();
  expect(users.length).toBeGreaterThanOrEqual(2);
});

test("重复邮箱应该报错", async () => {
  expect(async () => {
    await createUser("Alice2", "alice@example.com");
  }).toThrow();
});

// 性能测试
test("批量插入性能", () => {
  const start = performance.now();
  
  const insert = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
  for (let i = 0; i < 1000; i++) {
    insert.run(`User${i}`, `user${i}@example.com`);
  }
  
  const elapsed = performance.now() - start;
  console.log(`插入 1000 条记录耗时: ${elapsed}ms`);
  
  // 断言:在 Bun 中,1000 条记录应该在 50ms 内插入完成
  expect(elapsed).toBeLessThan(50);
});

运行测试:

bun test

Bun 测试框架的特色功能

  1. 并行测试执行:默认情况下,Bun 会并行运行测试文件(每个文件在独立的 JavaScript 上下文中执行)
  2. 内置 Mockbun:test 提供了 mock 函数,无需额外配置
  3. 快照测试:支持 Jest 风格的快照测试
  4. 覆盖率报告bun test --coverage 生成代码覆盖率报告
  5. 监听模式bun test --watch 监听文件变化,自动重新运行测试

4.3 打包:从源码到生产

Bun 内置了打包器(基于 esbuild 的架构思想,但用 Zig 重写),可以将你的项目打包为:

  • 单文件可执行程序(类似 Go)
  • 优化的 JavaScript 包(用于浏览器或 Node.js)
  • 多平台二进制(macOS、Linux、Windows)

打包为单文件可执行程序

# 打包为单文件可执行程序
bun build src/index.ts --compile --outfile my-api

# 指定目标平台
bun build src/index.ts --compile --target=bun-linux-x64 --outfile my-api-linux
bun build src/index.ts --compile --target=bun-darwin-arm64 --outfile my-api-mac

# 包含静态资源
bun build src/index.ts --compile --assets=public/ --outfile my-api

这有什么用?

  • 部署时不需要安装 Node.js/Bun
  • 不需要 npm install(所有依赖都打包进去了)
  • 分发简单:直接复制一个文件就行

对比一下:

Node.js 应用部署:
1. 安装 Node.js(匹配版本)
2. 复制源码
3. 运行 npm install(可能需要编译原生模块)
4. 启动应用
= 复杂,容易出错

Bun 单文件部署:
1. 复制单个可执行文件
2. 运行
= 简单,可靠

第五章:从 Node.js 迁移到 Bun 的生产级实战

5.1 兼容性现状:Bun 能替代 Node.js 吗?

这是大家最关心的问题。简短的回答是:对于大多数项目,可以;对于复杂项目,需要测试

Bun 承诺兼容:

  • 90% 的 Node.js API(包括 fspathhttpstreamcrypto 等核心模块)
  • npm 生态(可以直接使用 npm 包)
  • Web APIfetchWebSocketReadableStream 等)

但「兼容」不等于「完全相同」。以下是一些常见的迁移坑点:

坑点 1:原生 Node.js 模块的路径问题

// Node.js 代码(可以工作)
import { join } from "path";
const configPath = join(__dirname, "../config.json");

// Bun 中,__dirname 的行为可能不同
// 解决方案:使用 import.meta.dir(标准 ESM)
const configPath = join(import.meta.dir, "../config.json");

坑点 2:二进制原生模块(Native Addons)

如果你的项目依赖了原生模块(比如 bcryptcanvasgrpc),迁移到 Bun 可能会遇到问题。

原因:
Node.js 的原生模块使用 N-API(Node.js API for native addons)
Bun 实现了 N-API 的「大部分」功能,但不是全部

解决方案:
1. 检查原生模块是否提供了纯 JavaScript 替代(比如用 bcryptjs 替代 bcrypt)
2. 向 Bun 团队报告兼容性问题(他们修复得很快)
3. 暂时保留在 Node.js 上(如果原生模块不可替代)

坑点 3:环境变量处理

// Node.js 中,你可以直接修改 process.env
process.env.MY_VAR = "value"; // 可以工作

// Bun 中,process.env 是只读的(为了性能优化)
// 解决方案:使用 Bun.env
Bun.env.MY_VAR = "value";

5.2 迁移实战:一个 Express 应用的迁移

让我们迁移一个真实的 Express 应用。

原 Node.js 项目

// app.js (Node.js + Express)
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
require("dotenv").config();

const app = express();

app.use(cors());
app.use(helmet());
app.use(express.json());

app.get("/api/health", (req, res) => {
  res.json({ status: "ok", timestamp: Date.now() });
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

迁移到 Bun

// app.ts (Bun 原生)
import { serve } from "bun";

// Bun 内置了 CORS 和 Helmet 的等效功能
const app = serve({
  port: 3000,
  
  // CORS(内置支持)
  cors: true,
  
  // 安全头部(类似 Helmet)
  headers: {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "Content-Security-Policy": "default-src 'self'",
  },

  async fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/api/health") {
      return Response.json({
        status: "ok",
        timestamp: Date.now(),
      });
    }

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

console.log(`Server running on port ${app.port}`);

关键变化

  1. 不需要 expresscorshelmetdotenv 等依赖
  2. 使用 Bun 原生的 serve API
  3. 性能提升:Bun 版本比 Express 版本快约 3 倍(因为更少的抽象层)

5.3 性能对比:真实基准测试

我在本地做了一个简单的基准测试:一个返回 JSON 的 HTTP API。

测试环境

  • CPU: Apple M3 Pro (11 cores)
  • RAM: 18GB
  • Node.js: v22.21.1
  • Bun: v1.3.9

测试工具wrk(HTTP 基准测试工具)
测试命令wrk -t12 -c400 -d30s http://localhost:3000/api/health

结果

运行时请求/秒 (RPS)平均延迟99% 延迟
Node.js + Express45,2318.2ms42ms
Node.js + Fastify78,4594.7ms28ms
Bun (原生)132,5672.8ms15ms
Deno (原生)89,2344.1ms25ms

结论:Bun 的性能优势是真实的,不是营销数字。


第六章:Bun 的生态整合与未来展望

6.1 Bun 与主流框架的兼容性

Next.js

Bun 对 Next.js 的支持非常好。你可以使用 Bun 来:

  • 运行 Next.js 开发服务器(bun dev 替代 npm run dev
  • 打包 Next.js 应用(bun build 替代 npm run build
  • 运行 Next.js 生产服务器
# 使用 Bun 运行 Next.js
bun add next react react-dom
bun dev # 比 npm run dev 快 3 倍

注意:Next.js 16+ 对 Bun 有更好的支持。如果你在使用 older 版本,可能会遇到一些兼容性问题。

Remix

Remix 对 Bun 的支持是「一等公民」——Remix 团队与 Bun 团队有合作,确保兼容性。

# 创建 Remix + Bun 项目
bun create remix --template remix-template

Astro

Astro 也可以使用 Bun 作为运行时。

bun create astro

6.2 Bun 在企业级应用中的考量

安全性

Bun 的安全策略与 Node.js 不同:

  • 默认没有沙箱:Node.js 可以通过 --unsafe-perm 等标志控制权限,Bun 暂时没有等效功能
  • 原生模块的安全性:由于 Bun 是用 Zig 写的,它的攻击面与 Node.js 不同。需要关注 Bun 本身的安全漏洞

建议

  • 在生产环境中,使用 Docker 容器隔离 Bun 进程
  • 定期更新 Bun 到最新版本
  • 使用 Bun 的内置安全扫描器 API(Bun.Security

长期支持(LTS)

Bun 目前没有官方的 LTS 版本策略。这意味着:

  • 版本更新可能引入破坏性变更
  • 生产环境需要更谨慎地更新

建议

  • 锁定 Bun 版本(在 bun.lockb 中)
  • 在 staging 环境充分测试后再更新生产环境
  • 关注 Bun 的 GitHub 仓库和发布公告

6.3 Bun 的未来:2026 及以后

根据 Bun 团队的路线图,以下是值得期待的功能:

  1. Bun 2.0:预计 2026 年底发布,将带来:

    • 完整的 WebAssembly 支持(目前是实验性的)
    • 更好的 Windows 支持(目前 Windows 支持是 beta 质量)
    • 更多原生模块(比如 Bun.graphql,内置 GraphQL 服务器)
  2. Bun Cloud:一个基于 Bun 的 Serverless 平台(类似 Vercel,但是为 Bun 优化的)

  3. Bun Desktop:使用 Bun + Tauri 构建跨平台桌面应用的工具链


第七章:总结与思考

7.1 Bun 的技术价值

Bun 不是一个「更快的 Node.js」,而是一个重新思考 JavaScript 工具链的尝试。它的核心价值在于:

  1. 统一架构:所有工具共享同一套基础设施,避免了碎片化
  2. 性能极致:从 Zig 到 JavaScriptCore 到 SIMD,每个环节都针对性能优化
  3. 开发者体验:「电池内置」意味着更少的配置、更快的迭代

7.2 Bun 的适用场景

Bun 非常适合

  • 新的项目(从头开始,没有迁移成本)
  • 对性能敏感的应用(API 服务器、实时应用)
  • 全栈 JavaScript 应用(使用 React/Next.js 等框架)

Bun 可能不适合

  • 依赖大量原生模块的项目
  • 需要长期支持(LTS)的企业应用
  • 团队对新技术持保守态度的项目

7.3 个人思考:JavaScript 工具链的未来

Bun 的出现,让我想起了 Rust 对 C++ 的颠覆:不是通过「更好」来取胜,而是通过「重新思考整个工具链」来取胜。

Rust 没有试图「改进 C++」,而是说:「如果我们从头设计一个系统级编程语言,它会是什么样子?」——结果就是 Rust。

Bun 也在做同样的事情:「如果我们从头设计一个 JavaScript 工具链,它会是什么样子?」——结果就是 Bun。

我的预测

  • 到 2027 年,Bun 将成为 JavaScript 工具链的「默认选择」(就像 Rust 在系统编程中的地位)
  • Node.js 不会消失,但会变成「遗留系统运行时」(就像 Python 2 的地位)
  • Deno 会继续存在,但更多作为一个「实验场」,而不是生产级方案

附录 A:Bun 常用命令速查表

# 安装 Bun
curl -fsSL https://bun.sh/install | bash

# 项目初始化
bun init

# 安装依赖(比 npm install 快 25 倍)
bun install

# 运行脚本
bun run src/index.ts

# 测试
bun test
bun test --watch  # 监听模式
bun test --coverage  # 覆盖率报告

# 打包
bun build src/index.ts --outdir ./dist
bun build src/index.ts --compile --outfile my-app  # 单文件可执行程序

# 格式化代码(内置 Prettier 替代)
bun fmt

# 代码检查(内置 ESLint 替代)
bun lint

# 启动 REPL
bun repl

# 查看 Bun 版本
bun --version

# 更新 Bun
bun upgrade

附录 B:Bun vs Node.js vs Deno 详细对比

特性BunNode.jsDeno
引擎JavaScriptCoreV8V8
编写语言ZigC++Rust
启动速度~80ms~300ms~150ms
HTTP 性能~130k RPS~45k RPS~90k RPS
TypeScript 支持原生需要转译原生
包管理器内置npm(外部)内置(URL 导入)
测试框架内置外部(Jest 等)内置
打包器内置外部(webpack 等)内置
npm 兼容性兼容原生通过 npm 模块兼容
安全模型无沙箱无沙箱默认沙箱
LTS 策略
Windows 支持Beta完整完整

附录 C:进一步学习资源

  • 官方文档:https://bun.sh/docs
  • GitHub 仓库:https://github.com/oven-sh/bun
  • Discord 社区:https://bun.sh/discord
  • 性能基准:https://bun.sh/benchmarks
  • 迁移指南:https://bun.sh/guides/upgrade/from-node

本文写于 2026 年 6 月,基于 Bun v1.3.9。随着 Bun 的快速发展,部分内容可能会过时。建议读者关注官方文档获取最新信息。

如果你觉得这篇文章对你有帮助,欢迎在程序员茄子(chenxutan.com)上关注我,我会持续输出深度技术文章。

推荐文章

PHP如何进行MySQL数据备份?
2024-11-18 20:40:25 +0800 CST
PHP 命令行模式后台执行指南
2025-05-14 10:05:31 +0800 CST
rangeSlider进度条滑块
2024-11-19 06:49:50 +0800 CST
Gai:AI 原生的 Go Web 全栈框架
2026-05-21 16:19:43 +0800 CST
JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
在Rust项目中使用SQLite数据库
2024-11-19 08:48:00 +0800 CST
防止 macOS 生成 .DS_Store 文件
2024-11-19 07:39:27 +0800 CST
JavaScript设计模式:装饰器模式
2024-11-19 06:05:51 +0800 CST
程序员茄子在线接单