ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Bun v1.3 全栈实战:一个二进制搞定开发、打包与测试

Bun v1.3 全栈实战:一个二进制搞定开发、打包与测试 简介Bun v1.3 全栈 JavaScript 运行时发布配套代码包面向全栈开发团队、追求高性能的企业级应用开发者及希望从 Node.js 迁移的工程师。该版本将前端热重载、生产构建、MySQL/PostgreSQL/SQLite 数据库客户端与 Redis 客户端整合进单一运行时Redis 吞吐量达 250 万次/秒约为 ioredis 的 7.9 倍并优化了 WebSocket、包管理与测试调试能力可减少多工具切换成本。资源包共 5 个文件以 3 个 html 页面为主辅以 .inscode 与 .gitignore 配置压缩后约 11KB结构轻量便于快速查看示例页面与项目配置。目前已有 124 人学习下载。读者可借助其中的演示页面理解 Bun 全栈能力的前端呈现方式结合配置项了解项目初始化与忽略规则为搭建高性能全栈应用或评估迁移方案提供参考。1. Bun v1.3 全栈 JS 运行时一个二进制文件把开发服务器、打包器和测试跑通如果你之前对 Bun 的印象还停留在「一个更快的 npm 替代品」那 v1.3 这个版本值得重新看一眼。它把运行时、包管理器、打包器、测试运行器、开发服务器全部塞进同一个二进制文件里你不需要再维护tsconfigjest.configwebpack.confignodemon.json这一整套配置文件。装完 Bunbun run dev就能起一个带热更新的全栈服务bun test直接跑测试bun build出产物。对独立开发者和中小团队来说这意味着从零到能跑的原型时间从半天压缩到十几分钟。这篇笔记不讲发布会通稿只讲我实际把 Bun v1.3 用在一个全栈项目里的路径怎么装、怎么配、哪些参数必须调、哪些地方会翻车。适合已经会写 JS/TS、想减少工具链维护成本的从业者。2. 从零跑通 Bun v1.3安装、初始化与第一个全栈服务2.1 安装 Bun 与验证运行时版本Bun 的安装方式在不同系统上略有差异但核心逻辑都是把单个二进制放到 PATH 里。Linux 和 macOS 用官方脚本Windows 目前推荐用 WSL 或原生安装包。我一般先确认版本因为 v1.3 的很多 API 在早期版本里不存在版本不对后面全是玄学报错。# Linux / macOS 安装 curl -fsSL https://bun.sh/install | bash # 验证版本必须确认是 1.3.x bun --version # 输出示例1.3.0 # 查看内置工具链是否齐全 bun --help安装脚本会把bun放到~/.bun/bin如果终端提示找不到命令检查~/.bashrc或~/.zshrc里有没有把该目录加进 PATH。这一步看起来简单但我在 CI 环境里踩过坑容器镜像里预装的 Bun 是 1.0 版本bun run能跑但Bun.serve的某些选项不生效排查了半小时才发现是版本问题。所以任何环境里第一步永远是bun --version。2.2 用 bun init 生成项目骨架Bun 自带初始化命令不需要npm init再手动改。它会根据当前目录情况生成package.json、tsconfig.json和入口文件。我一般会加--yes跳过交互在脚本化场景里更稳。# 创建项目目录并初始化 mkdir bun-fullstack-demo cd bun-fullstack-demo bun init --yes # 查看生成的文件 ls -la # package.json tsconfig.json index.ts README.md生成的tsconfig.json默认已经针对 Bun 运行时做了优化比如moduleResolution设为bundlertypes里包含bun-types。如果你从 Node 项目迁移过来不要直接复制原来的tsconfig否则类型解析会出问题。package.json里默认的module字段是index.ts这意味着 Bun 直接跑 TS 文件不需要先编译成 JS。这是 Bun 和 Node 最本质的区别之一它内置了 TS 转译器用的是 Zig 写的原生解析器速度比ts-node快一个数量级。2.3 写一个带 API 和静态页面的最小全栈服务Bun v1.3 的Bun.serve支持直接返回 HTML、JSON 和文件不需要额外装 Express 或 Fastify。下面这个例子同时提供 API 路由和静态页面能直接跑起来。// index.ts const server Bun.serve({ port: 3000, // 开发模式下开启热更新 development: true, async fetch(req) { const url new URL(req.url); // API 路由返回 JSON if (url.pathname /api/health) { return Response.json({ status: ok, runtime: Bun ${Bun.version}, timestamp: Date.now(), }); } // 静态页面直接返回 HTML if (url.pathname /) { return new Response( !DOCTYPE html html headtitleBun v1.3 Demo/title/head body h1Bun v1.3 全栈服务已启动/h1 p idstatus检查 API 中.../p script fetch(/api/health) .then(r r.json()) .then(d document.getElementById(status).textContent JSON.stringify(d)); /script /body /html, { headers: { Content-Type: text/html; charsetutf-8 } } ); } return new Response(Not Found, { status: 404 }); }, }); console.log(服务运行在 http://localhost:${server.port});启动命令是bun run index.ts或者直接在package.json里配dev: bun run index.ts然后bun run dev。development: true这个参数很关键开启后 Bun 会启用热重载修改文件后浏览器自动刷新同时错误页面会显示源码位置。生产环境要把它设为false否则会暴露堆栈信息。port默认是 3000如果被占用会报EADDRINUSE改成 3001 或让 Bun 自动选端口传port: 0即可。2.4 用 bun test 跑第一个测试用例Bun 内置的测试运行器兼容 Jest 的 API但不需要装jest、ts-jest、types/jest这一堆东西。测试文件命名规则是*.test.ts或*_test.ts放在项目任意位置都行。// health.test.ts import { describe, expect, test, beforeAll, afterAll } from bun:test; let server: ReturnTypetypeof Bun.serve; beforeAll(() { // 启动一个测试用的服务实例 server Bun.serve({ port: 0, // 让系统分配空闲端口 fetch(req) { const url new URL(req.url); if (url.pathname /api/health) { return Response.json({ status: ok }); } return new Response(Not Found, { status: 404 }); }, }); }); afterAll(() { server.stop(); }); describe(健康检查接口, () { test(返回 200 和 ok 状态, async () { const res await fetch(http://localhost:${server.port}/api/health); expect(res.status).toBe(200); const data await res.json(); expect(data.status).toBe(ok); }); test(未知路径返回 404, async () { const res await fetch(http://localhost:${server.port}/unknown); expect(res.status).toBe(404); }); });运行bun test即可输出格式和 Jest 类似但启动时间通常在 100ms 以内。port: 0是测试里的常用技巧避免和开发服务器抢端口。beforeAll和afterAll的用法和 Jest 一致但 Bun 的测试运行器是原生实现的不支持 Jest 的全部插件生态比如jest.mock的某些高级用法需要改用mock.module。如果你的项目重度依赖 Jest 插件迁移前先跑一遍测试看兼容性。3. Bun v1.3 的打包器与包管理bun build 和 bun install 的参数怎么调3.1 bun install 的缓存机制与 lockfile 策略Bun 的包管理器是它最早出圈的功能v1.3 里安装速度依然是最快的但真正影响团队协作的是 lockfile 策略。Bun 默认生成bun.lockb二进制格式v1.3 开始也支持bun.lock文本格式后者在 code review 时更友好。# 安装依赖使用文本 lockfile bun install --save-text-lockfile # 从零安装忽略缓存 bun install --no-cache # 只安装生产依赖 bun install --production # 强制重新解析所有依赖版本 bun install --force--save-text-lockfile这个参数我建议在团队项目里默认开启因为二进制 lockfile 在 Git diff 里看不出变化合并冲突时基本没法手动解决。文本 lockfile 虽然体积大一点但可读性和可维护性好得多。--no-cache在 CI 里偶尔需要比如缓存损坏导致安装出诡异错误时清掉重来比排查快。--production用于构建镜像阶段能减少最终产物体积。注意 Bun 的缓存目录默认在~/.bun/install/cache如果磁盘空间紧张可以用BUN_INSTALL_CACHE_DIR环境变量改到其他位置。3.2 bun build 打包全栈项目的三个关键参数bun build是 v1.3 里被低估的功能。它不只是打包前端代码还能把服务端代码打包成单个可执行文件。下面是一个典型的前端打包命令。# 打包前端入口输出到 dist 目录 bun build ./src/index.tsx \ --outdir ./dist \ --target browser \ --minify \ --sourcemapexternal \ --splitting \ --format esm--target browser告诉 Bun 按浏览器环境解析依赖会自动 polyfill 或排除 Node 内置模块。--minify开启压缩生产环境必加。--sourcemapexternal生成独立的 sourcemap 文件方便线上排查但不会增大主包体积。--splitting开启代码分割对多页面应用很重要否则所有路由的代码会打成一个巨大文件。--format esm输出 ES 模块格式现代浏览器都支持。如果你要打包服务端代码成可执行文件用--target bun和--compile# 打包成单个可执行文件 bun build ./index.ts --compile --outfile ./my-server # 运行产物 ./my-server--compile会把 Bun 运行时和你的代码一起打包生成的文件可以直接在没装 Bun 的机器上运行。这个特性在部署时非常省事不需要目标机器有 Node 或 Bun 环境。但要注意如果代码里用了动态import()或读取外部文件打包后路径会变需要用import.meta.dir来定位资源。3.3 用 bun run 管理脚本与并行任务bun run不只是npm run的替代品它支持并行执行和依赖声明。在package.json里可以这样配{ scripts: { dev: bun run --watch index.ts, build: bun build ./src/index.tsx --outdir ./dist --minify, test: bun test, check: bun run typecheck bun run test, typecheck: bunx tsc --noEmit } }--watch参数让 Bun 监听文件变化并自动重启比nodemon轻量得多。bunx是 Bun 版的npx可以直接跑 npm 包里的命令不需要先全局安装。bun run check会按顺序执行typecheck和test如果typecheck失败就不会跑测试这在 CI 里能省时间。注意bun run执行脚本时用的是 Bun 运行时不是 Node所以脚本里可以直接用Bun.file、Bun.write这些 API。4. 避坑与排查Bun v1.3 迁移中容易翻车的五个地方4.1 现象bun install 后 node_modules 里缺少某些包原因Bun 默认使用硬链接和全局缓存来加速安装某些包如果缓存损坏或权限不对硬链接会失败但安装命令不报错。解决先bun install --no-cache强制重新下载如果还不行删掉node_modules和bun.lockb再装。检查~/.bun/install/cache的磁盘权限确保当前用户有写权限。4.2 现象Bun.serve 的 development 模式在 Docker 里不生效原因development: true依赖文件系统监听Docker 容器里 inotify 的默认限制可能不够。解决在docker run时加--ulimit nofile65536:65536或者在宿主机上调整fs.inotify.max_user_watches。如果还不行改用轮询模式在Bun.serve里传watch: { mode: poll }代价是 CPU 占用略高。4.3 现象bun test 报错 “Cannot find module bun:test”原因测试文件被其他工具比如 VS Code 的 TS 插件用 Node 的类型定义解析了找不到 Bun 特有的模块。解决在tsconfig.json的compilerOptions.types里加上bun-types并确保moduleResolution是bundler。如果用的是 monorepo每个子包的tsconfig都要加不能只加根目录的。4.4 现象bun build --compile 生成的二进制文件运行时报 “ENOENT”原因代码里用了相对路径读取文件比如fs.readFileSync(./config.json)打包后工作目录变了。解决改用import.meta.dir拼接绝对路径或者把配置文件通过--define注入成常量。如果必须读外部文件用Bun.file(import.meta.dir /config.json)。4.5 现象从 Node 迁移后 process.env 读不到变量原因Bun 默认不自动加载.env文件需要显式指定或使用--env-file参数。解决启动时加bun run --env-file.env index.ts或者在代码里用Bun.env代替process.envBun 会自动读取.env。注意.env.local和.env.production的加载顺序和 Node 生态的dotenv不同迁移时要把环境变量文件整理清楚。5. 进阶技巧用 Bun v1.3 的 SQLite 和 S3 API 做全栈数据层Bun v1.3 内置了bun:sqlite和Bun.s3这两个 API 让全栈项目不需要额外装数据库驱动和对象存储 SDK。下面是一个用 SQLite 做数据持久化、用 S3 兼容接口存文件的例子。// db.ts import { Database } from bun:sqlite; const db new Database(app.db, { create: true }); // 建表IF NOT EXISTS 保证幂等 db.run( CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, created_at INTEGER DEFAULT (unixepoch()) ) ); // 预编译语句避免 SQL 注入 const insertPost db.prepare( INSERT INTO posts (title, content) VALUES (?, ?) RETURNING id, title, created_at ); const listPosts db.prepare( SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT ? ); export function createPost(title: string, content: string) { return insertPost.get(title, content); } export function getPosts(limit 20) { return listPosts.all(limit); }bun:sqlite的 API 和better-sqlite3很像但不需要编译原生模块安装即用。db.prepare返回的语句对象可以重复执行性能比每次拼 SQL 好。RETURNING子句在 SQLite 3.35 支持Bun 内置的版本足够新。注意unixepoch()是 SQLite 的函数返回秒级时间戳前端展示时要乘 1000。// storage.ts const s3 new Bun.S3Client({ accessKeyId: Bun.env.S3_ACCESS_KEY!, secretAccessKey: Bun.env.S3_SECRET_KEY!, bucket: my-app-bucket, endpoint: Bun.env.S3_ENDPOINT, // 兼容 MinIO 等自建服务 }); export async function uploadFile(key: string, file: File) { await s3.write(key, file.stream(), { type: file.type, // 设置缓存头减少重复请求 cacheControl: public, max-age31536000, }); return s3.presign(key, { expiresIn: 3600 }); } export async function getFileUrl(key: string) { return s3.presign(key, { expiresIn: 3600 }); }Bun.S3Client兼容 AWS S3 协议所以 MinIO、Cloudflare R2 这些都能用。presign生成带签名的临时 URL适合前端直接上传或下载。expiresIn单位是秒默认 86400我一般设 3600 避免链接被滥用。注意Bun.env读取环境变量时如果变量不存在会返回undefined加!是告诉 TS 这里一定有值但运行时如果真没有会报错所以.env文件要配好。验证这套数据层是否正常可以写一个集成测试// db.test.ts import { test, expect, beforeEach } from bun:test; import { Database } from bun:sqlite; let db: Database; beforeEach(() { // 每个测试用独立的内存数据库互不干扰 db new Database(:memory:); db.run(CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT)); }); test(插入并查询帖子, () { db.run(INSERT INTO posts (title) VALUES (?), [测试标题]); const row db.query(SELECT title FROM posts WHERE id 1).get(); expect(row).toEqual({ title: 测试标题 }); });用:memory:做测试数据库是 SQLite 的经典用法速度快且不留痕迹。db.query和db.prepare的区别是前者每次返回新语句后者可复用。测试里用哪个都行但生产代码里预编译语句更合适。我自己的习惯是任何新项目先用 Bun 跑通最小闭环再逐步把 Node 生态的依赖替换成 Bun 内置 API。替换过程中遇到不兼容的包不要硬改先用bunx跑原工具等 Bun 生态跟上再换。这套路径帮我省掉了大量配置时间但前提是版本必须锁在 1.3.x并且每次升级前跑一遍完整测试。希望帮到你。本文还有配套的精品资源点击获取
返回列表