搭一个最小 monorepo

要点

  • 前三篇把认知层的东西讲完了
  • 一个最小版 monorepo 要摆清楚的就是三层:对外运行的应用、跨应用共享的代码、仓库公共配置
  • 这一刀看着普通,但它提前解决了后面最容易打架的边界问题
  • 根目录该放什么:根 package.json、workspace 配置、turbo.json、根 tsconfig、lint/format 这类全局配置
  • 一上来就把 apps/web、apps/admin、apps/docs、apps/worker

内容

1. 第一步不是写配置,是决定目录怎么摆

前三篇把认知层的东西讲完了。

从这里开始动手。

搭 monorepo 最先要做的不是去写 turbo.json,而是决定:仓库里哪些东西算应用,哪些算共享包,哪些配置放在根目录。

第一次搭目录,最常见的两个极端:

  • 太保守——还是按单仓库项目的方式组织,后面长起来又要整体搬一次
  • 太激进——一上来就分出一堆目录和包,项目还没长起来,结构先复杂了

这一篇只搭一个最小可工作的版本,不追求完整。

2. 最小 monorepo:先把三层分开

一个最小版 monorepo 要摆清楚的就是三层:对外运行的应用、跨应用共享的代码、仓库公共配置。

apps 独立运行的应用

web 面向用户的前端

api 接口服务

packages 跨应用共享的代码

ui 共享组件

shared 共享类型和工具

package.json 仓库根脚本和依赖

pnpm-workspace.yaml 声明 workspace 范围

turbo.json 任务编排和缓存规则 apps 放能独立启动、独立运行的东西。packages 放不会自己对外跑、但会被多个应用复用的东西。

Canvas actions56%Exit zen mode

Drawing canvas

3. 为什么第一刀切在 apps 和 packages 之间

这一刀看着普通,但它提前解决了后面最容易打架的边界问题。

不切这一刀会怎样:

  • 应用里的业务代码被"顺手共享"出去,共享包里塞满了只服务某一个应用的东西
  • 共享代码一直没地方放,每个应用各复制一份,改一处要跟好多处

先分开,规则就很简单:应用代码先待在应用里,只有真正跨应用复用的,才往 packages 里提。

4. 根目录只放全仓库共用的东西

根目录该放什么:根 package.json、workspace 配置、turbo.json、根 tsconfig、lint/format 这类全局配置。

根目录该像一个"仓库层",而不是某个具体应用的业务目录。如果根目录里开始混进只属于某一个 app 的东西,说明有东西放错了位置。

5. 不要一次把未来的目录全建出来

一上来就把 apps/webapps/adminapps/docsapps/worker,再加 packages/uipackages/sharedpackages/configpackages/promptspackages/schemaspackages/ai-core 全分出来——如果项目现在还很小,这样做只会让仓库先变复杂。

先把真正已经存在的边界放出来,后面按需长,比一开始把架子搭满更稳。

6. 这套结构什么时候够用

如果你的项目里前端和 API 已经是两个明确的应用,又开始出现重复使用的类型、工具函数或组件,不想再靠复制代码维护共享逻辑——这套最小结构就够起步了。

它最大的好处不是"看起来专业",而是后面的 workspace、本地包引用、Turborepo 任务编排,都有了一个清晰的落点。

7. 总结

第一次搭 monorepo,最重要的不是目录分多细,而是先把最容易长期演进的边界摆出来

apps + packages + 根目录公共配置,三层分开,就够往下走了。下一篇看 workspace 怎么把这些本地包连起来。