【实践】Monorepo 从0到1搭建最小可用 Vue Monorepo
写在前面:团队的项目需要使用 Monorepo,本文是我学习 Monorepo 架构的实操记录。选择以"最小可用"为切入点,不引入 Turborepo 等复杂工具,只聚焦 pnpm workspace 的核心机制,力求让每一步都清晰可复现。写好一篇有质量的博客真花时间啊。
一、背景介绍
1.1 为什么需要 Monorepo?
随着前端工程规模扩大,代码复用和协作效率成为核心痛点。传统的多仓库(Multi-repo)模式下,公共组件库、工具函数与业务应用分散在不同仓库中,版本同步、依赖管理和跨仓调试的成本越来越高。
Monorepo(单仓库多包管理)将多个相关项目置于同一个代码仓库中,通过统一的包管理器和工作空间机制,实现代码共享、依赖一致和构建协同。对于 Vue 生态而言,Monorepo 尤其适合以下场景:
- 组件库与业务应用共存:UI 组件和业务逻辑在同一仓库内迭代,修改组件后业务侧即刻感知
- 工具函数复用:通用的格式化、请求封装等逻辑无需单独发包即可跨项目消费
- 统一技术栈管理:TypeScript、ESLint、构建工具等配置可集中维护,降低维护成本
1.2 目标
目标是搭建一个最小可运行的 Vue Monorepo:
- 1 个 Vue 主应用(
apps/web) - 2 个内部包:
packages/ui(Vue 组件库)、packages/utils(纯 TS 工具包) - 主应用通过
workspace:*协议引用内部包,HMR 即时生效 - 根目录提供统一脚本入口:
pnpm dev、pnpm build、pnpm preview
有意暂不引入的内容(后续逐步展开):TypeScript 严格类型检查、ESLint / Prettier、Turborepo 任务编排、Changesets 版本发布。
1.3 前置环境
确保本地环境满足以下要求:
node-v# >= 18,建议 20 或 22pnpm-v# >= 8,建议 9.x本文实操环境:Node v22.22.2+pnpm 9.10.0。如未安装 pnpm,可执行:
npmi-gpnpm二、方案分析
在动手写代码之前,先对整体架构和关键技术决策做一次系统性梳理。
2.1 目录结构设计
monorepo-demo/ ├── apps/ │ └── web/ # Vue 主应用(可独立运行的产品,例如本团队的官网) └── packages/ ├── ui/ # Vue 组件包(被复用的库) └── utils/ # 通用工具包(被复用的库)设计原则:apps/*存放"可独立运行的产品",packages/*存放"被复用的库"。这是前端 Monorepo 的业界主流分法,职责边界清晰,便于后续扩展。
2.2 包命名约定
| 路径 | 包名 | 说明 |
|---|---|---|
apps/web | web | 主应用,无需 scope |
packages/ui | @repo/ui | 内部组件库,使用@reposcope 区分 |
packages/utils | @repo/utils | 内部工具包,使用@reposcope 区分 |
@repo/*仅为命名约定,不会发布到 npm,仅作为内部包名前缀,方便在代码中快速识别内部依赖。
2.3 关键技术决策
| 决策点 | 选择 | 理由 |
|---|---|---|
| 包管理器 | pnpm | Workspace 支持成熟,磁盘占用低,软链机制清晰 |
| 是否引入 Turborepo | 否 | 学习期保持工具最少集,先理解 pnpm workspace 本身 |
| 内部包是否需要构建 | 否(直接消费源码) | 主应用 Vite 直接转译 TS/Vue,HMR 立即生效 |
| Vue 在组件包中的依赖类型 | peerDependencies | 避免多 Vue 实例引发的 inject/hydration 异常 |
| TypeScript | 使用,但不严格校验 | 第一周聚焦"能跑通",类型基建放第二周 |
三、实操步骤
步骤 1:创建根目录配置
1.1 配置pnpm-workspace.yaml
packages:-"apps/*"-"packages/*"作用:声明哪些目录属于 workspace。pnpm 会将这些目录中的package.json识别为内部包,支持互相引用。
1.2 配置根目录package.json
{"name":"monorepo-demo","version":"0.0.0","private":true,"description":"Vue monorepo learning playground","scripts":{"dev":"pnpm --filter web dev","build":"pnpm -r --filter \"./packages/*\" --filter web run build","preview":"pnpm --filter web preview"},"engines":{"node":">=18"},"packageManager":"pnpm@9.10.0"}关键点解析:
"private": true:根包不会被误发布到 npm--filter web:将命令转发给apps/web执行-r --filter "./packages/*" --filter web run build:递归构建所有packages/*,再构建web;pnpm 会按依赖图自动排序,确保被依赖的包先构建packageManager:锁定 pnpm 版本,避免团队成员因版本差异导致 lockfile 漂移
1.3 配置.gitignore
node_modules dist .DS_Store *.log .vscode/* !.vscode/extensions.json .env .env.local注意:Monorepo 中node_modules会出现在多个层级(根目录 + 每个子包),使用通配符*可自动应用到所有子目录,无需重复声明。
步骤 2:创建packages/utils(先打通最简流程)
2.1packages/utils/package.json
{"name"