使用 pnpm 构建 Monorepo 实战指南
1. 项目背景与目标
本文记录了使用 Vue3 创建一个示例集合项目的完整过程。这个项目旨在记录 Vue3 的使用案例、错误案例以及相关 demo,采用了 monorepo 结构组织代码,以便更好地管理多个相关但独立的示例。
2. 项目初始化与技术选型
2.1 核心技术栈
技术选型
框架: Vue 3
语言: TypeScript
构建工具: Vite
包管理工具: pnpm
项目组织: Monorepo (pnpm workspace)
路由: Vue Router 4
目录结构设计
在规划项目结构时,考虑了两种主要分类方式:
第一种结构:在 demos 下进一步细分
packages/ ├── demos/ │ ├── composition-api/ │ ├── pinia-store/ │ ├── vue-router/ │ └── 其他具体demo... ├── best-practices/ └── error-cases/
第二种结构:直接在 packages 下放置所有 demo(扁平结构)
packages/ ├── demo-composition-api/ ├── demo-pinia-store/ ├── demo-vue-router/ ├── best-practice-performance/ ├── error-case-lifecycle/ └── 其他packages...
经过讨论,最终决定采用简化后的第一种结构,将示例分为两大类:
packages/
├── plugins/ # 插件使用说明/案例
│ ├── vue-router/
│ ├── pinia/
│ └── 其他插件...
└── demos/ # 功能演示(包含错误案例及补救方法)
├── composition-api/
├── lifecycle/
└── 其他功能性demo...
这种结构的优点是分类明确,容易查找,同时插件使用和功能演示有清晰的区分。
2.3 项目创建步骤
创建基础项目:
pnpm create vite . --template vue-ts pnpm install pnpm add vue-router@4
创建工作空间结构:
mkdir packages cd packages mkdir plugins demos
配置工作空间:
创建
pnpm-workspace.yaml
文件:packages: - "packages/**/*"
创建示例项目:
cd packages/demos pnpm create vite demo-composition-api --template vue-ts cd ../plugins pnpm create vite plugin-vue-router --template vue-ts
3. 常见问题与解决方案
问题 1:子项目依赖重复安装
问题描述:在创建子项目时,每个项目都安装了自己的依赖,导致依赖重复。
解决方案:将共享依赖移到根目录的package.json
中,删除子项目中的重复依赖。
更新前的子项目 package.json:
{
"dependencies": {
"vue": "^3.5.13",
"vue-router": "4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"typescript": "~5.7.2",
"vite": "^6.2.0",
"vue-tsc": "^2.2.4"
}
}
更新后的子项目 package.json:
{
"name": "@vue3-example/plugin-vue-router",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite --port 3001",
"build": "vue-tsc && vite build",
"preview": "vite preview"
}
}
问题 2:项目配置文件的有效性
问题描述:担心将依赖移至根目录后,子项目的配置文件如vite.config.ts
和tsconfig.json
会失效。
解决方案:子项目的配置文件仍然有效,因为这些配置文件是基于项目路径的。为了验证这一点,我们在每个子项目的vite.config.ts
中设置了不同的端口:
// packages/plugins/plugin-vue-router/vite.config.ts
export default defineConfig({
plugins: [vue()],
server: {
port: 3001,
},
});
另外,还可以在package.json
的 scripts 中通过命令行参数设置端口:
"scripts": {
"dev": "vite --port 3001"
}
问题 3:工作空间命令执行
问题描述:如何避免进入每个子项目目录来启动它们?
解决方案:在根目录的package.json
中添加特定的启动命令:
"scripts": {
"dev:composition-api": "cd packages/demos/demo-composition-api && pnpm dev",
"dev:lifecycle": "cd packages/demos/demo-lifecycle && pnpm dev",
"dev:router": "cd packages/plugins/plugin-vue-router && pnpm dev",
"dev": "pnpm -r dev",
"build": "pnpm -r build",
"preview": "pnpm -r preview",
"clean": "pnpm -r clean",
"type-check": "pnpm -r type-check"
}
这样可以从根目录直接启动特定的子项目。
关于 -r
选项:pnpm -r
是 --recursive
的缩写,用于递归地执行命令。这意味着当你在根目录运行带有 -r
选项的命令时,pnpm
会在所有子项目中递归执行该命令。这种方式可以方便地在整个 monorepo 中执行相同的命令,而不需要手动进入每个子项目目录。
问题 4:依赖安装位置的影响
问题描述:如果依赖安装在子包 A 中,子包 B 是否可以使用?
解决方案:在 pnpm 中,依赖是严格隔离的。如果依赖只安装在子包 A 中,子包 B 将无法直接使用该依赖。为了让多个子包共享依赖,应该在工作区根目录安装这些依赖。使用以下命令在根目录安装共享依赖:
pnpm add ts-node typescript -w
关于 -w
标志:-w
是 --workspace
的缩写,用于在整个工作区范围内安装依赖。这意味着安装的依赖会被放置在工作区的根目录下,所有子包都可以访问这些依赖。这种方式可以减少重复安装,确保所有子包使用相同版本的依赖,并简化依赖管理。
4. 项目优化与完善
添加清理和类型检查脚本
为了更好地管理项目,我们添加了以下脚本:
"scripts": {
"clean": "pnpm -r clean",
"clean:all": "rimraf node_modules && rimraf packages/*/node_modules && rimraf packages/*/dist",
"type-check": "pnpm -r type-check"
}
同时在每个子项目中添加对应的脚本:
"scripts": {
"clean": "rimraf dist",
"type-check": "vue-tsc --noEmit"
}
版本控制和 GitHub 集成
初始化 Git 仓库:
git init git add . git commit -m "初始化项目:Vue3示例集合"
创建 GitHub 仓库并推送代码:
git remote add origin https://github.com/SSF0/vue3-example.git git branch -M main git push -u origin main
5. Monorepo 实践经验总结
Monorepo 优势:
共享依赖,减少重复安装
便于管理多个相关但独立的项目
统一版本控制和配置
pnpm 工作空间使用技巧:
使用
pnpm-workspace.yaml
定义工作空间在根目录添加共享依赖
使用
pnpm -r
或pnpm --filter
执行命令# 递归执行命令 pnpm -r dev # 指定执行命令的子包 pnpm --filter @vue3-example/00-starter dev
使用
pnpm -w
安装工作区依赖
项目配置最佳实践:
使用命名空间(如
@vue3-example/*
)命名子项目在根目录添加辅助脚本简化操作
为每个子项目设置不同的端口避免冲突
命名空间的价值:
避免命名冲突:在 npm 中,包名必须全局唯一。使用命名空间(如
@A/utils
和@B/utils
)可以避免不同团队发布同名包时的冲突。清晰的项目结构:命名空间帮助在 monorepo 中更清晰地组织和管理包,便于识别和管理。
私有包管理:命名空间可以用于创建私有包,确保内部工具不会被意外发布到公共 npm 注册表。
版本管理:命名空间允许独立发布和更新每个包,而不影响其他包。
Vue3 学习方向:
组合式 API 的基本使用
生命周期钩子的变化
路由配置与使用
状态管理整合
关于 Monorepo 和命名空间的关系:
Monorepo 特点:在单一代码库中管理所有子项目,方便代码共享、版本控制和依赖管理。
命名空间在 Monorepo 中的作用:即使在 monorepo 中,使用命名空间仍然是一个好习惯,特别是在发布到 npm 时。命名空间可以确保即使在不同的 monorepo 中,包名也不会冲突。
避免同名包:在 monorepo 中,通常通过命名规范和代码审查来避免同名包的创建。
通过这个项目,我们搭建了一个灵活的 Vue3 示例集合,可以方便地添加、测试和展示各种 Vue3 功能和最佳实践。
6. 项目发展规划
添加更多 Vue3 特性的示例
集成 Pinia 状态管理示例
添加常见错误案例及解决方案
完善文档和注释
通过这个项目,我们搭建了一个灵活的 Vue3 示例集合,可以方便地添加、测试和展示各种 Vue3 功能和最佳实践。
7. 项目地址
本项目已发布到 GitHub,欢迎访问、star 和提交 issues:
- GitHub 仓库:https://github.com/SSF0/vue3-example
如有任何问题或建议,请通过 GitHub issues 反馈。