技术没有银弹,但好的脚手架能让你少走三年弯路。

本文将带你从零理解 frontend-monorepo-starter 这个项目的设计理念、架构分层、技术选型和上手操作,适合任何想搭建企业级前端 Monorepo 的开发者。

🔗 项目仓库:https://github.com/1648298170/frontend-monorepo-starter (文末有详细引导,建议先 Star 收藏,方便随时回看)


一、这个项目是什么?为什么值得看?

一句话概括:这是一个面向"真实业务、长期维护"而设计的前端 Monorepo 模板(starter / 脚手架)。

市面上很多 Monorepo 教程只教你怎么把多个项目塞进一个仓库(“多包同仓”),但实际企业开发中真正头疼的是这些问题:

  • ❌ 新增一个应用,要重新配一遍 ESLint、TypeScript、Vite……配置总是对不齐
  • ❌ React 和 Vue 团队共用一段逻辑,互相 copy,改一处忘改另一处
  • ❌ 依赖版本各搞各的,React 18 和 19 混用,莫名报错
  • ❌ 项目越来越大,构建越来越慢,不知道哪里可以复用、哪里该拆分
  • ❌ 新人来了,不知道代码该往哪个目录放,"约定"全靠口头传授

frontend-monorepo-starter 用一套清晰的分层 + 自动化工具链,把上面这些问题一次性解决。它不是"炫技项目",而是可以直接拿来开新业务的实战模板

它的核心能力一览

能力实现
多应用共存同一仓库内同时管理 React 应用和 Vue 应用
跨框架共享React/Vue 共用框架无关逻辑(工具函数、请求、权限、配置)
统一工程规范一套 ESLint / TypeScript / Prettier / Stylelint 配置全仓库复用
依赖版本统一pnpm catalog 集中管理所有生态依赖版本
一键代码生成内置非交互式生成器,命令行生成应用/组件/页面/Store
质量保障单元测试 Vitest + 端到端 Playwright + 死代码检测 Knip
高效构建Turborepo 任务编排 + 缓存,Vite 8 构建

二、核心理念:把"分层"刻进骨头里

理解这个项目,只需要理解一个核心思想:分层 + 依赖方向

1. 四个代码区域,各司其职

项目把所有代码划分成四个区域,每个区域职责单一:

apps/          ← 应用层(薄):组装路由、页面、布局
packages/      ← 共享层(厚):沉淀可复用能力
  shared/      ← 框架无关(React/Vue 都能用)
  react/       ← React 专属
  vue/         ← Vue 专属
  tooling/     ← 工程配置(ESLint/TS/Vite)
scripts/       ← 仓库级脚本(生成器、校验、版本管理)
docs/          ← 架构文档、开发规范

用大白话解释

  • apps/ 里的应用只负责"组装",不沉淀业务逻辑。就像搭乐高,应用是拼好的成品,但每个零件都来自 packages/
  • packages/shared/最纯粹的逻辑——比如格式化日期、发请求、判断权限。它们不认识 React,也不认识 Vue,所以两边都能用。
  • packages/react/packages/vue/和框架绑定的能力——比如 React 的 UI 组件、Vue 的 composables。
  • packages/tooling/工程配置——全仓库共享的 ESLint 规则、tsconfig,改一处全局生效。

2. 严格的依赖方向

这是整个架构的"灵魂约束":

app → pages → features → app shared → packages

翻译成人话就是依赖只能"向外",不能"向内"绕

  • ✅ 应用可以用 packages 里的能力
  • ✅ React 应用可以用 shared 里的工具函数
  • ❌ React 应用不能用 Vue 包里的东西(反之亦然)
  • ❌ shared 包不能依赖 React 或 Vue(否则就没法跨框架复用)
  • ❌ 应用 A 不能直接 import 应用 B 的代码

这些规则不是写在文档里靠自觉,而是由 ESLint 的 @repo/eslint-config/boundaries 规则自动检查! 你违规了,lint 直接报错,根本提交不了代码。

3. 包的"公开 API"约定

每个 workspace 包都通过 src/index.ts 暴露对外接口,并且 package.jsonexports 字段锁死入口。

// ✅ 推荐:通过包名导入
import { formatDate } from "@repo/utils";

// ❌ 禁止:深层路径导入,包内部结构调整会直接破坏调用方
import { formatDate } from "@repo/utils/src/date";

这个约定看着简单,却是 Monorepo 长期可维护的关键:包内部怎么重构都行,只要 index.ts 的导出不变,调用方完全无感知。


三、技术栈全景图

这个项目的技术选型非常"克制且前沿",每个选择都有明确理由:

分类技术为什么选它
包管理pnpm 10 + workspace硬链接节省磁盘,workspace 原生支持 monorepo
依赖版本治理pnpm catalog一个文件统一所有依赖版本,告别版本混乱
任务编排Turborepo智能缓存 + 并行构建,build 自动按依赖拓扑排序
构建工具Vite 8 + Rolldown极速冷启动和 HMR,Rolldown 是新一代打包器
React 栈React 19 + React Router + Zustand最新的 React + 极简状态管理(无 Provider 模板)
Vue 栈Vue 3 + Vue Router + Pinia官方推荐全家桶
样式Tailwind CSS 4 + Sass + Design Token原子化 CSS + 主题变量 + 规范化检查
代码质量TypeScript 6 + ESLint + Prettier类型安全 + 代码风格统一
测试Vitest(单元)+ Playwright(E2E)Vite 原生测试 + 真实浏览器端到端测试
死代码检测Knip自动找出没被引用的导出,保持仓库干净

重点说一下 pnpm catalog(依赖目录)

这是 pnpm 比较新的特性,也是这个项目的"依赖治理杀手锏"。

传统痛点:React、Vue、各种插件分散在每个应用的 package.json 里,升级 React 要改十几个文件,还容易漏。

catalog 方案:所有生态依赖的版本统一写在 pnpm-workspace.yaml

catalog:
  react: ^19.2.1
  vue: ^3.5.25
  zustand: ^5.0.9

各应用在自己的 package.json 里只写 "react": "catalog:"实际版本由 catalog 决定

升级 React?只改 catalog 一处,然后 pnpm install,全仓库同步。 这就是"单一数据源"的威力。

注意:catalog 只统一版本,不改变依赖归属。哪个应用用 axios,还得在自己 package.json 声明,不会因为 catalog 登记了就全局安装。


四、共享能力层详解(packages/shared)

这是项目最精华的部分。packages/shared 下有 5 个框架无关的包,它们是 React 和 Vue 应用共享的"地基":

1. @repo/utils — 纯工具函数

最简单的包,比如日期格式化:

export function formatDate(value: Date | string | number, locale = "zh-CN") {
  const date = value instanceof Date ? value : new Date(value);
  return new Intl.DateTimeFormat(locale, {
    year: "numeric", month: "2-digit", day: "2-digit",
  }).format(date);
}

不依赖任何框架,React 和 Vue 直接 import { formatDate } from "@repo/utils" 就能用。

2. @repo/request — 框架无关请求客户端

这是个设计得非常克制的请求封装。核心是 createRequestClient 工厂函数:

const client = createRequestClient({
  baseUrl: "/api",
  timeoutMs: 30000,
  getToken: () => authService.getToken(),  // 应用注入鉴权
  onRequest: (ctx) => { /* 请求拦截 */ },
  onResponse: (res, ctx) => { /* 响应拦截 */ },
  onError: (err, ctx) => { /* 错误监控 */ },
});

设计亮点

  • 基于浏览器原生 fetch,不绑定 axios/ofetch
  • 超时控制AbortController,自动清理定时器避免内存泄漏
  • 业务取消 + 超时取消双信号合并(AbortSignal.any
  • 错误归一化:非 2xx 转成统一的 HttpError,超时转成 RequestTimeoutError
  • 可测试:测试时注入 mock fetch,无需真实网络

这套设计让"发请求"这件事完全脱离框架,React 和 Vue 用同一套客户端,各自只管注入鉴权策略。

3. @repo/auth — 权限判断

核心函数 hasPermission,纯逻辑判断,不依赖状态库:

export function hasPermission(
  grantedPermissions,    // 用户已拥有的权限
  requiredPermissions,   // 目标需要的权限
  mode = "all"           // "all" 全部满足 | "any" 满足任一
) {
  if (requiredPermissions.length === 0) return true;  // 无要求=公开
  // ...用 Set 做 O(1) 查找
}

应用层(React 的 Provider 或 Vue 的路由守卫)只要传入权限集合,复用同一套判断逻辑。

4. @repo/config — 运行时配置解析

createAppConfig(env) 负责把 Vite 注入的环境变量解析成结构化的配置对象,并做校验

export function createAppConfig(env: RuntimeEnv): AppConfig {
  const appName = readNonEmptyString("VITE_APP_NAME", env.VITE_APP_NAME, "Frontend App");
  const apiBaseUrl = readNonEmptyString("VITE_API_BASE_URL", env.VITE_API_BASE_URL, "/api");
  validateApiBaseUrl(apiBaseUrl);  // 必须是绝对 URL 或以 / 开头
  return { appName, apiBaseUrl, environment: normalizeEnvironment(...) };
}

配置错误在启动时就抛出,而不是运行到一半才崩。

5. @repo/constants — 共享常量

放业务无关的常量,比如环境标识、枚举值。

框架适配层(packages/react、packages/vue)

shared 里的能力是"裸"的,框架适配层负责把它们"翻译"成 React/Vue 能直接用的形态:

shared 包React 适配Vue 适配
auth@repo/react-auth(Provider/Hook)@repo/vue-auth(composable/directive)
utils/ui@repo/react-ui(组件)@repo/vue-ui(组件)

五、应用内部结构(apps)

React 和 Vue 应用采用相同的业务分层概念,但各自遵循框架原生用法:

src/
  app/              ← 应用装配层(只组装,不写业务)
    App.tsx/App.vue
    router/         ← 路由模块化
    store/          ← 应用级状态(侧边栏、主题等)
    runtime/        ← 启动时创建的服务单例
  pages/            ← 路由页面(组合 feature,处理路由参数)
  features/         ← 业务能力(登录、订单、权限...)
    <feature>/
      components/
      hooks/ 或 composables/
      model/
      api/
  hooks/            ← React 应用级 Hook
  composables/      ← Vue 应用级 Composable
  shared/           ← 应用内共享(非 Hook/Composable)
  styles/

状态管理的"分层哲学"

这是很多团队踩坑的地方。项目明确规定了什么状态该放哪

状态类型放在哪里例子
组件局部状态框架原生状态(useState/ref)弹窗输入、按钮 loading
Feature 状态features/<feature>/store/编辑器草稿、多步骤流程
应用级状态app/store/侧边栏、主题、通知中心
URL 可分享状态写进路由 URL,别进 Store筛选、分页、排序、当前 Tab
服务端数据请求缓存方案,别复制到 Store列表数据、详情

关键原则:不是所有状态都要进全局 Store!能用 URL 表达的就用 URL(刷新可恢复、可分享),表单输入留在组件里。Store 只放"跨组件跨页面且无法放进 URL"的状态。

路由模块化

路由不是一坨写在一个文件里,而是按业务域拆分

app/router/
  index.ts          ← 只负责组合
  routes/
    home.routes.ts
    account.routes.ts
    system.routes.ts

页面默认动态导入(懒加载),路由文件不执行 API 请求、不在顶层读 Store——保持路由文件的"纯粹"。


六、代码生成器:告别手搓模板文件

这是提升开发效率的利器。项目内置了非交互式代码生成器

pnpm g --help      # 查看帮助(g 是 generate 的缩写)

支持生成的类型

类型作用
app生成完整 React/Vue 业务应用
component生成组件(应用级/Feature/共享)
feature生成业务 Feature 入口
page生成页面(不自动改路由)
storeReact 生成 Zustand,Vue 生成 Pinia
hook / composable生成 React Hook / Vue Composable

实战示例

生成一个新应用

pnpm g app \
  --name admin-web \
  --framework react \
  --display-name "运营管理后台"

生成 Feature 内的组件

pnpm g component --app react-web --feature order --name order-list

生成器的设计原则(非常严谨)

  • 🛡️ 生成前展示完整文件计划,让你看清要生成什么
  • 🛡️ 已有文件一律拒绝覆盖,不会误删你的代码
  • 🛡️ 默认生成单元测试--skip-test 可关闭
  • 🛡️ 支持 --dry-run,只校验不写盘
  • 🛡️ 页面生成不自动改路由——避免工具瞎猜权限和布局
  • 🛡️ 未知参数直接报错——防止拼写错误被静默忽略

这种"非交互式 + 严格校验"的设计,让它既适合本地开发,也适合接入 CI 脚本自动化调用


七、快速上手:从零启动项目

1. 环境准备

工具版本要求
Node.js>=22.12.0
pnpm10.18.3(唯一允许的包管理器)

推荐启用 Corepack 自动管理 pnpm 版本。

2. 安装依赖

pnpm install

⚠️ 必须用 pnpm,禁用 npm/yarn。项目有 preinstall 脚本自动校验运行时版本。

3. 启动应用

pnpm dev:react    # 启动 React 应用 → http://localhost:5174
pnpm dev:vue      # 启动 Vue 应用   → http://localhost:5173
pnpm dev          # 同时启动所有应用

端口由各应用的 .env 管理(不写死在 scripts 里)。端口冲突就改 .env.local

DEV_SERVER_PORT=5180

4. 开发代理配置

本地联调后端,在 .env 配置:

DEV_PROXY_PREFIX=/api
DEV_PROXY_TARGET=http://localhost:3000

Vite 会自动把 /api/xxx 转发到后端,并移除 /api 前缀。配置错误(前缀不以 / 开头、target 不是合法 URL)会在启动时直接报错


八、提交前必做的质量检查

项目提供了一套完整的验证命令矩阵:

pnpm format:check    # 格式检查
pnpm typecheck       # 类型检查
pnpm lint            # ESLint 检查(含依赖边界)
pnpm test            # 单元测试
pnpm build           # 构建
pnpm lint:unused     # 死代码检测(Knip)
pnpm verify:app-templates  # 验证应用模板(重型,CI 用)
pnpm test:e2e        # 端到端测试

这些命令背后是 Turborepo 的智能编排

  • build 会自动先构建它依赖的包(dependsOn: ["^build"]
  • 构建结果会被缓存,没改动的包直接用缓存,秒级完成
  • typechecktest 同样依赖构建产物

ESLint 边界检查实战

前面说的"依赖方向约束",具体是这样工作的。ESLint 配置会自动扫描 apps 目录,根据每个应用 package.json 的依赖判断它是 React 还是 Vue 应用,然后套用对应规则和边界约束。

你如果在一个 Vue 应用里 import 了 React 包,或者在 shared 包里 import 了 Vue,lint 会立刻报错。架构约束被自动化了,不靠人肉 review。


九、这个项目的设计哲学总结

透析下来,这个项目有几条贯穿始终的哲学,非常值得学习:

1. 约定优于配置(Convention over Configuration)

目录怎么分层、代码放哪、包怎么导出——全部有明确约定。新人来了不用猜,照着约定走就行。

2. 约束靠工具保证,不靠自觉

依赖方向、版本统一、代码风格——全都有自动化工具(ESLint、catalog、Prettier)兜底。人都会犯错,但工具不会。

3. 薄应用,厚共享

应用只管组装,能力沉淀到 packages。新增应用时复用现成能力,不用重复造轮子。

4. 框架无关优先

能用纯逻辑解决的(请求、权限、配置、工具),绝不绑死框架。这是 React/Vue 共存的根基。

5. 显式优于隐式

包必须声明自己用的依赖、配置错误立即抛出、未知参数直接报错——宁可启动失败,也不要运行时玄学 bug。

6. 可测试、可验证

共享能力默认带单元测试,模板有 verify:app-templates 重型验证,端到端有 Playwright。每一层都能被独立验证。


十、适合谁用?怎么用?

✅ 强烈推荐给:

  • 要开新前端项目的技术负责人:直接 clone 改造,省掉 80% 的基建时间
  • React + Vue 混栈团队:它的跨框架共享设计正是为你准备的
  • 想学习企业级前端工程化的开发者:这是教科书级的 Monorepo 实践
  • 受够了自己项目配置混乱的人:看看"有约束"的项目长什么样

🚀 快速起步建议:

  1. 先 clone 仓库,跑通 pnpm dev
  2. docs/guides/project-guide.md(项目自带的使用指南,写得非常细)
  3. pnpm g 生成你的第一个 feature,感受生成器
  4. 按业务需要,把 shared 包的能力扩展成自己的业务基建

十一、进阶实战:用 Git Worktree 让多分支并行开发丝滑无比

到这里,项目本身的架构、理念、用法已经讲透了。最后再送你一个进阶锦囊——把 Git Worktree 和这个 Monorepo 结合,能让多分支并行开发效率翻倍。很多团队没用过这个特性,但它其实早就内置在 Git 里了。

1. 先搞懂:什么是 Git Worktree?

一个痛点场景:你在 main 分支开发新功能,写到一半,同事说线上有个紧急 bug 要修。你只能:

  • 😩 git stash 暂存当前改动(容易忘 stash 了什么)
  • 😩 切到 hotfix 分支修 bug
  • 😩 切回 maingit stash pop 恢复(冲突就头疼了)

而且,Monorepo 项目尤其痛苦:切分支后,node_modules 可能要重新装,Vite 缓存失效,启动又变慢。

Git Worktree 的解法:让你同一个仓库,在磁盘上同时存在多个工作目录,每个目录对应一个分支,互不干扰。

frontend-monorepo-starter/          ← 主工作区(main 分支)
  apps/ packages/ ...
frontend-monorepo-starter-hotfix/   ← worktree(hotfix 分支)
  apps/ packages/ ...
frontend-monorepo-starter-feat/     ← worktree(feature 分支)
  apps/ packages/ ...

三个目录,三个分支,各装各的依赖、各跑各的 dev server,切换上下文只是切 IDE 窗口而已。再也不用 stash 来 stash 去!

2. 和 Monorepo 项目结合:为什么是绝配?

frontend-monorepo-starter 这种项目有几个特点,正好和 worktree 互补:

特点传统分支切换的痛点Worktree 的优势
依赖体量大切分支后 pnpm 链接结构可能变化每个 worktree 独立 node_modules,互不影响
构建缓存(Turborepo)切分支缓存频繁失效各 worktree 独立缓存,构建快
多应用同时跑dev server 端口打架不同 worktree 各跑各的端口
跨框架协作React/Vue 改动频繁切换一个 worktree 专心改 React,另一个改 Vue

用大白话:Monorepo 项目"重"(依赖多、构建久),切换成本高。Worktree 用空间换时间,让每个分支都有自己"干净独立的工作台"。

3. 实战操作:5 分钟上手

前提:先把项目 clone 到本地(假设主目录叫 frontend-monorepo-starter)。

第①步:创建一个 worktree(开新分支)
# 在主仓库目录下执行
git worktree add ../frontend-monorepo-starter-hotfix hotfix/login-bug

这条命令做了三件事:

  1. ../frontend-monorepo-starter-hotfix 创建一个新目录
  2. hotfix/login-bug 分支检出到这个目录
  3. 这个目录和主仓库共享同一个 .git(不是完整 clone,省空间)
第②步:在新 worktree 里装依赖
cd ../frontend-monorepo-starter-hotfix
pnpm install        # 独立安装,不影响主工作区

💡 省时技巧:pnpm 用的是硬链接 + 全局 store,所以即使是新 worktree,pnpm install 也很快——实际文件大多是从全局 store 链接过来的,不会真的下载。

第③步:启动开发
pnpm dev:react      # 在 hotfix worktree 里跑 React 应用

现在你同时打开两个 IDE 窗口:主目录继续写新功能,hotfix 目录专心修 bug,互不打架。

第④步:干完活,清理 worktree
# 回到主仓库目录
cd ../frontend-monorepo-starter

# 查看当前所有 worktree
git worktree list

# 删除用完的 worktree(分支还在,只是工作目录删了)
git worktree remove ../frontend-monorepo-starter-hotfix

4. 配合本项目的最佳实践

基于这个 Monorepo 的结构,推荐这套 worktree 使用姿势:

① 用约定俗成的目录命名,一眼区分

git worktree add ../fms-hotfix   hotfix/xxx      # 紧急修复
git worktree add ../fms-feat     feature/xxx    # 新功能
git worktree add ../fms-review   review/xxx     # Code Review 专用

② 不同 worktree 用不同端口,避免冲突

本项目的端口由 .env 控制,给每个 worktree 设不同端口:

# fms-hotfix/.env.local
DEV_SERVER_PORT=5181

# fms-feat/.env.local
DEV_SERVER_PORT=5182

这样多个 worktree 的 dev server 可以同时跑,端口不打架。

③ 依赖目录用 pnpm,别用 npm/yarn

这点 AGENTS.md 里反复强调。pnpm 的 store 机制让 worktree 的 pnpm install 接近"秒装",换 npm 就要老老实实重新下载,worktree 的省时优势就没了。

④ 把 node_modules 和构建产物加进 worktree 的忽略

worktree 是普通目录,.gitignore 仍然生效,node_modulesdist.turbo 都不会被 git 跟踪,放心用。

5. 常见命令速查表

# 创建 worktree(新分支)
git worktree add <路径> -b <新分支名>

# 创建 worktree(已有分支)
git worktree add <路径> <已有分支名>

# 查看所有 worktree
git worktree list

# 删除 worktree
git worktree remove <路径>

# 修剪元数据(删除已不存在的 worktree 记录)
git worktree prune

# 移动 worktree
git worktree move <旧路径> <新路径>

6. 一句话总结

Git Worktree + pnpm Monorepo = 多分支并行开发的"空间换时间"最优解。

每个 worktree 都是独立的工作台:独立依赖、独立缓存、独立端口。你在写新功能的同时,随手就能切到另一个 worktree 修紧急 bug,再也不用 stashstash 去了。

🔗 想体验这套丝滑流程?先把项目 clone 下来试试:https://github.com/1648298170/frontend-monorepo-starter


🌟 写在最后:去仓库点个 Star 吧!

这篇文章只是把这个项目的"骨架"讲清楚,真正的精华在源码和文档里——这个项目的 docs/ 目录写得极其详尽,覆盖了架构、规范、CI、教学文档,本身就是一份值得收藏的前端工程化学习资料。

📌 GitHub 仓库https://github.com/1648298170/frontend-monorepo-starter

强烈建议你

  1. 点个 Star——这是对作者最直接的支持,也能方便你以后随时找到
  2. 🍴 Fork 或 Clone——直接作为下一个项目的起点
  3. 📖 仔细读 docs/——里面的设计文档比很多付费课程都讲得透
  4. 💡 Watch 仓库——跟进后续更新,这个项目还在持续演进

好的工程实践值得被更多人看到。如果你在搭建前端 Monorepo 时遇到过配置混乱、复用困难、协作痛苦的问题,这个项目会给你一套经过深思熟虑的答案


如果这篇文章对你有帮助,欢迎点赞、收藏、转发!有问题欢迎在评论区交流,也可以去 GitHub 仓库提 Issue。

仓库地址再放一次,别忘了 Star:👉 https://github.com/1648298170/frontend-monorepo-starter

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐