前端开发与 HTML Imports

用 Bun 的全栈开发服务器直接运行 HTML、TypeScript 与 CSS,并正确产出生产构建

最后更新于

什么时候用,什么时候不用

场景建议
新项目原型、内部工具、小型 SPA直接用 Bun 的 HTML imports,零配置
想把 API 和前端放进同一个进程Bun.serve 的 routes 里挂 .html
已有 Vite/框架项目、依赖其插件生态继续用原工具链,见 生态选型决策
需要 SSR、流式渲染、服务端组件使用框架(Next、TanStack Start 等),不用本页形态

直接运行 HTML

bun ./index.html

Bun 会解析 HTML 并自动打包其中引用的脚本、样式和静态资源;TypeScript 与 JSX 默认可用,读取 tsconfig.json

  • SPA 模式:单个 .html 会成为所有路由的回退,客户端路由(如 /users/123)正常工作。
  • MPA 模式:传入多个文件或 glob(./**/*.html),Bun 按公共前缀为每个文件建路由。

开发时启用热重载(React 项目即 Fast Refresh):

bun --hot ./index.html

--console 可以把浏览器 console 转发到终端;Chrome DevTools 的 Automatic Workspace Folders 支持从浏览器直接保存改动。

与 API 同进程

server.ts
import index from './index.html';

Bun.serve({
  routes: {
    '/': index,
    '/api/health': () => Response.json({ ok: true }),
  },
  development: true, // 开发模式:HMR 与更详细的错误
});

插件与环境变量

bun add -d bun-plugin-tailwind
bunfig.toml
[serve.static]
plugins = ["bun-plugin-tailwind"]
env = "PUBLIC_*"

env = "PUBLIC_*" 只会把匹配前缀的环境变量内联进浏览器产物——这是边界,不是形式:任何不带 PUBLIC_ 前缀的变量都不该出现在前端 bundle 里,服务端密钥更不能为了"方便"改前缀。

生产构建

bun build ./index.html --minify --outdir=dist --env=PUBLIC_*

[serve.static]env 只作用于开发服务器;生产构建必须显式传 --env=PUBLIC_*(或 Bun.buildenv 选项),否则 process.env.PUBLIC_* 不会被替换。两个细节:替换只识别字面量 process.env.FOO 写法;未设置的变量会原样保留,浏览器里可能抛 ReferenceError: process is not defined——构建后在产物里 grep 一遍 process.env 确认替换完整。

注意:插件只能通过 bunfig.toml(开发服务器)或 Bun.build API 使用,bun build CLI 不支持插件参数。构建产物是纯静态文件,交给 CDN 或任意静态托管;--compile --target=browser 可进一步打出单个自包含 HTML。

SPA 回退只是开发服务器行为

bun ./index.html 的全路由回退不会随静态产物带走。生产托管必须自己配置重写规则(未命中路径返回 /index.html),否则深链接刷新返回 404。每家 CDN/静态托管的写法不同,按平台文档配置并用深链接实测。

边界

  • 这套能力替代的是"esbuild + 静态服务器 + 手动 HMR"的组合,不是 Vite 生态;迁移前清点你实际用到的 Vite 插件。
  • 开发服务器和生产构建共享打包器,但代理、缓存头和压缩仍是你的部署层职责。
  • 浏览器产物不得包含服务端密钥;用 PUBLIC_ 前缀纪律加 code review 双重约束。

验收

  1. --hot 下改 React 组件,页面状态保留地刷新;
  2. bun build 产物在无 Bun 环境的静态服务器上完整可用;
  3. 产物里 grep 不到任何非 PUBLIC_ 前缀的服务端变量,也没有残留的 process.env
  4. 托管层的重写规则生效:深链接刷新返回 index.html 而不是 404。

官方参考:HTML imports 与前端开发服务器全栈开发打包器