结论:遗留单体系统改monorepo不是“把代码挪进一个仓库”就完事,核心卡点在依赖解耦、构建隔离和ci流水线重写——90%的失败都栽在这三块上。

直接说结论:遗留单体系统改 Monorepo 不是“把代码挪进一个仓库”就完事,核心卡点在依赖解耦、构建隔离和 CI 流水线重写——90% 的失败都栽在这三块上。
如何判断你的单体系统是否适合拆进 Monorepo
别急着动 git init,先看这几点是否成立:
- 代码里存在明确的逻辑边界(比如
src/backend和src/frontend目录长期独立演进,有不同维护人) - 已有部分模块被其他项目以
npm link或本地路径方式引用(说明它已具备“包”的雏形) - 构建产物可分离(例如前端能单独
build出静态文件,后端能打包成 jar 或 docker 镜像) - CI 脚本里已有按子目录触发构建的逻辑(比如
if [[ $CHANGED_FILES =~ ^src/backend/ ]]; then ...)
如果四条里只满足 1–2 条,建议先做模块化重构再进 Monorepo;全满足,才能往下走。
pnpm workspace + 独立 package.json 的最小可行结构
用 pnpm 是因为它的硬链接机制对遗留系统更友好——不用改原有 require() 路径,也不用全局安装依赖。
根目录 package.json 必须包含:
{
"private": true,
"type": "module",
"workspaces": [
"packages/backend",
"packages/frontend",
"packages/shared"
]
}
每个子包(如 packages/backend)必须有自己完整的 package.json,且 name 字段不能重复(哪怕只是 @myorg/backend),否则 pnpm install 会静默失败。
关键细节:
-
shared包的exports字段要显式声明导出路径,避免 Node.js 的 ESM/CJS 混用报错 - 所有子包的
scripts里不要写cd ../.. && npm run xxx—— workspace 会自动解析workspace:*协议,直接写"build": "tsc -p tsconfig.json"就行 - 遗留系统若用
node_modules里的全局 bin(如webpack),需在子包里显式devDependencies声明,pnpm 不会自动 hoist
Git 提交历史迁移时最常踩的坑
用 git-filter-repo 拆分历史时,以下错误会导致后续 CI 失效:
- 没加
--force但又在非 fresh clone 上运行,命令直接退出不报错 -
--path写成src/backend/而不是packages/backend/,结果抽出来的 commit 里路径全是错的 - 遗漏
--refs refs/heads/* --refs refs/tags/*,导致 feature 分支和 v1.2.0 tag 全丢 - 拆分后没删掉原仓库的
.gitmodules文件,新仓库git clone会卡在 submodule 初始化
验证是否成功:在拆出的新仓库里运行 git log --oneline | head -n 5,每条 commit message 应该和原始仓库里对应路径的修改一致,且 git status 显示 clean。
CI 流水线必须重写的三个地方
旧单体系统的 CI 往往是“全量构建”,Monorepo 下必须改成按变更影响范围执行:
- 检测变更路径:GitHub Actions 用
github.event.pull_request.patch解析 diff,GitLab CI 用CI_MERGE_REQUEST_DIFF_BASE_SHA获取 base commit,不能只靠git diff --name-only HEAD~1 - 跳过未变更包:Turborepo 的
turbo run build --filter=backend依赖pipeline配置,漏配dependsOn会导致 shared 包更新后 backend 不重建 - 部署目标分离:前端产物推 CDN 用
aws s3 sync dist/ s3://my-fe-bucket/,后端镜像推 registry 用docker push my-registry/backend:v1.2.3,二者不能共用同一个 deploy job
最容易被忽略的是:旧系统里 .env.production 这类配置文件通常放在根目录,迁入 Monorepo 后必须移到对应子包内,或改用 dotenv + process.env.NODE_ENV 动态加载,否则构建时会漏环境变量。











