第3章:Monorepo 工程结构
本章从仓库物理布局出发,讲清 pnpm workspace 如何同时管理 Go 后端与 React 前端、根 package.json 承担什么职责、以及 cmd / internal / docker / docs 的边界约定。理解目录约定后,任何新成员都能凭文件路径推断职责归属。
流程
仓库根目录只有六个条目:backend/、frontend/、docs/、LICENSE、根 package.json、pnpm-workspace.yaml。pnpm 把 frontend(还有未来可能的其他 JS 包)纳入统一 workspace 管理;Go 后端自带 go.mod,与 pnpm 互不干涉——两者通过根 package.json 的 scripts 串成一条命令链(例如一键安装、一键构建)。
后端内部遵循 Go 社区标准布局:cmd/ 放可执行入口(webserver 主服务、envinit 初始化工具、test 试验场),internal/ 放全部私有代码(编译器强制禁止外部导入,天然模块边界),docker/ 放镜像与编排,migrations/ 放 SQL 参考脚本,backend/docs/ 放 swaggo 生成的 Swagger 产物。前端的 src/ 按 pages / services / utils / components / layouts / routes / context / hooks 分层,与后端的 handler / service 命名形成心智对仗。
输入是「一个人维护全栈」的现实,输出是一套前端一条 pnpm 命令、后端一条 go 命令即可各自开发测试的并行工作流:
flowchart TD
R[仓库根] --> B[backend: go.mod 自治]
R --> F[frontend: pnpm workspace 包]
R --> P[根 package.json: 串联脚本]
B --> C1[cmd/webserver · cmd/envinit]
B --> C2[internal/ 12 个领域包]
F --> C3[src/pages 业务页面]
F --> C4[src/services API 层]
实现
几个值得注意的工程细节:
.github/workflows/ci.yml在根目录而非 backend,说明 CI 视角是整个 monorepo(第 50 章)。.husky/pre-commit+ lint-staged 提交前只对暂存文件跑 lint/format,前端代码质量门禁前移。backend/internal/config/config.yaml被 gitignore,仓库提供config.yaml.example:本地密钥永不入库,示例文件与新配置项同步维护是仓库纪律(README 明确要求)。.cursor/rules/toast-notify.mdc:为 AI 辅助编码准备的规则文件,约束前端提示组件的使用方式——工具链配置也是工程的一部分。cmd/test/main.go充当临时实验入口,避免污染正式命令;正式入口保持极薄(webserver 的 main 只有装配逻辑,见第 5 章)。
前端的路径别名 ~/ 指向 src/(vite.config.mts + tsconfig.json 双处配置),所以 import 全是 ~/services/http 风格,杜绝 ../../ 相对路径漂移。
📌 设计决策
- pnpm workspace 而非把前端塞进根 package.json:版本与依赖隔离清晰,未来可加 e2e 包、shared-types 包。
- backend 与 frontend 完全不共享构建产物:前后端仅通过 OpenAPI 契约(Swagger 页面)耦合,是典型的契约优先协作。
internal/而非pkg/(顶层):所有代码都是应用私有,没有对外开源 SDK 的诉求。
代码示例
workspace 定义极简,只圈定 JS 侧:
# pnpm-workspace.yaml
packages:
- frontend
根 package.json 把两侧常用动作收敛为脚本入口(节选示意):
{
"name": "live-mixer",
"private": true,
"scripts": {
"dev": "pnpm --filter base-ui dev",
"build": "pnpm --filter base-ui build",
"test": "pnpm --filter base-ui test"
},
"devDependencies": {
"husky": "^9.0.0",
"lint-staged": "^15.5.1"
}
}
小结
- 仓库 = Go 自治模块 + pnpm workspace 前端 + 根级 CI/Husky 门禁。
cmd/薄入口、internal/强边界、配置示例文件同步是三条硬纪律。- 前后端仅通过 OpenAPI 契约协作,目录结构对仗便于全栈切换。
思考题
- 若要抽出前后端共享的 TypeScript 类型(按 Swagger 生成),workspace 结构应如何扩展?
cmd/test这种试验入口长期存在会有什么风险?更好的替代是什么?
项目信息
- GitHub仓库:github.com/Chyona/live-mixer
- 项目案例:gogoshine.com