小工具、内网后台、边缘盒子常想要:一个产物、一个端口、拷上去就能跑。做法是前端 build 进 dist/,编译期嵌进后端,由同一 HTTP 服务既提供 API 又提供静态页。本文只比这类嵌入方案,不讨论 Nginx 反代双进程、也不讨论桌面壳(Tauri / Wails)。
| 方案 | 嵌入方式 | 真·单文件 | 体积体感 | 热更新前端 | 成熟度 | 优缺点简述 |
|---|---|---|---|---|---|---|
Go embed | //go:embed + embed.FS,http.FileServer / 框架挂载 | 是(静态链接二进制) | 小~中 | 需重编 | 很高 | 官方、简单、运维友好;改 UI 必重新编译发布。 |
Rust rust-embed / include_dir | 宏把目录打进二进制,Axum 等挂静态 | 是 | 小~中 | 需重编 | 高 | 体积与性能好;构建链路比 Go 重一点。 |
| .NET 单文件发布 | wwwroot 或 Embedded Resource + PublishSingleFile | 基本是(原生库可能解压旁路) | 中 | 需重发 | 高 | 企业栈顺手;单文件有解压/裁剪细节要盯。 |
| Spring Boot Fat JAR | resources/static(或 frontend-maven 打进 jar) | 是(一个 jar,非原生二进制) | 偏大 | 需重打 jar | 很高 | Java 生态默认路径;要 JVM,冷启动与体积不如 Go。 |
Bun --compile | compile 时可带入资源 / 服务 dist | 是(可执行文件) | 中 | 需重编 | 中 | TS 全栈一条龙;生态与生产案例仍少于 Go。 |
Deno compile | 编译进可执行文件,静态一并带上 | 是 | 中 | 需重编 | 中 | 权限模型清晰;部署心智接近 Bun。 |
| Node SEA / pkg / nexe | SEA 或第三方打单文件,再读内嵌资源 | 勉强(原生模块、路径坑多) | 中~大 | 需重打 | 中低 | 能做但不优雅;长期更看好 Bun/Deno 或干脆 Go。 |
Zig @embedFile | 编译期嵌入字节,自己挂 HTTP | 是 | 极小 | 需重编 | 小众 | 极致可控;要自己拼服务与 SPA fallback。 |
| Python(PyInstaller 等) | 打包时收集 static,运行时解包或 zip 内读 | 常是「单目录/单包」 | 大 | 需重打 | 中 | 能凑合;真单二进制体验明显弱于 Go/Rust。 |
共同约定(做之前先定)
- 路由:
/api/*走后端;其余优先静态文件;SPA 再fallback → index.html。 - 构建:CI 先
pnpm/npm build,再go build/cargo build/dotnet publish,把dist指到嵌入目录。 - 开发:本地仍用 Vite 代理到后端;只有 release 才 embed,避免每次改样式都全量重编。
- 缓存:嵌入资源可设长缓存 + 文件名 hash;
index.html短缓存或不缓存。
推荐
- 默认首选 → Go
embed:语法少、产物干净、社区范例最多(PocketBase、AList 类工具同思路)。 - 已在 Rust →
rust-embed+ Axum/Actix。 - 已在 .NET / Java → 分别走 单文件 + wwwroot、Fat JAR + static,不必为了「单端口」硬转 Go。
- TS 想一条命令出二进制 → 优先试 Bun compile,其次 Deno;Node SEA 当备选。
- 要极致小包 / 嵌入式 → Zig;要脚本胶水 → Python 能跑就行,别期待 Go 同款体验。
什么时候别嵌
- 前端独立高频发版、要走 CDN / 多环境静态托管。
- 前后端团队与发布节奏完全拆开。
- 需要水平扩展时只扩 API、静态全交给对象存储 / CDN。
这时回到「API 一端口 + 静态另部署 / 反代」更合适;嵌入适合 交付边界就是一个进程 的场景。