需求描述
公司刚好准备对项目进行重构,老项目包含多端以及含多个子应用,其中有很多冗余的代码,甚至还有小伙伴来问某个子应用的仓库在哪。基于这个契机,对升级项目进行了评估,从而动手对老项目进行了框架和架构升级。从vue2升级vue3采用了 GoGoCode,架构上升级采用了monorepo。
什么是 Monorepo 架构
Monorepo(Mono Repository)架构 是一种将多个项目、模块或服务存储在一个单一代码仓库中的软件架构设计模式。在 Monorepo 架构下,多个子项目可以共享一个版本控制系统(如 Git),并在同一个仓库中进行管理、协作和部署。
这种架构常常用于需要跨多个模块进行高度协作的大型项目,尤其是当这些模块之间有共享代码、依赖关系或相似功能时。
为什么要升级到 Monorepo
- 代码共享:可以方便地共享代码和模块。
- 一致性:所有模块都遵循相同的开发和构建流程。
- 版本控制:所有项目的版本可以统一管理。
- 简化依赖:跨模块的依赖管理变得更简洁
构建工具的选择
1. Lerna
Lerna 是一种经典的 monorepo 工具,广泛用于管理多个 JavaScript 包的开发、发布、版本管理等。
主要特点:
- 包管理与版本控制:通过
lerna bootstrap命令连接和安装包的依赖,可以选择统一版本管理或独立版本管理。 - 版本发布:Lerna 提供了自动化发布功能,支持发布多个包,并能管理版本号。
- 支持依赖关系管理:通过 bootstrapping 操作链接本地包依赖,确保依赖关系的正确性。
优点:
- 简单易用,适合中小型项目。
- 支持独立的版本管理,可以为每个包定义不同的版本号。
- 通过
lerna publish进行批量发布,减少手动操作。
缺点:
- 不直接优化构建性能,对大项目可能显得较慢,尤其是在多个包之间的依赖关系很复杂时。
- 缺少内置的缓存机制和增量构建支持,随着项目规模的增长,性能瓶颈可能会显现。
2. Turborepo
Turborepo 是一个现代化的构建系统,专门优化了 monorepo 架构中的构建性能、缓存机制以及增量构建。它特别适合需要高效构建和任务并行化的项目。
主要特点:
- 增量构建与缓存:通过智能缓存机制,避免重复构建,提高构建速度。
- 并行执行任务:支持并行执行多个构建、测试、lint 等任务,从而提高效率。
- 高效的构建流程:对常见的前端框架(如 React、Next.js)做了很好的集成,优化了构建过程。
优点:
- 极高的构建速度,适用于大型项目和复杂的工作流。
- 增量构建和任务缓存可以显著减少 CI/CD 时间。
- 可以轻松扩展,支持复杂的工作流,适合多样化的开发团队。
- 对 CI/CD 流程友好,节省云构建成本。
缺点:
- 学习曲线较陡,配置和使用需要一定的理解。
- 需要额外的工具和配置来处理包管理(如与 pnpm 一起使用)。
- 目前对 monorepo 的支持较强,但在一些非标准化的场景中,可能需要一些调整。
3. pnpm
pnpm 是一个快速、节省空间的包管理工具,特别适合管理 monorepo 中的依赖。它优化了依赖管理的效率,并且支持快速安装和磁盘空间节省。
主要特点:
- 硬链接优化:使用硬链接来共享依赖,避免重复下载,节省磁盘空间。
- pnpm workspaces:通过 workspaces 来管理 monorepo 中的多个包,支持包之间的依赖和版本管理。
- 快速安装与一致性:由于 pnpm 的缓存和安装机制非常高效,它的安装速度非常快,尤其适合依赖较多的项目。
优点:
- 极快的安装速度,节省磁盘空间,适合大型 monorepo 项目。
- 可以通过 pnpm-workspaces 管理多个包,避免了传统 npm 的依赖冲突问题。
- 简化了包版本的管理和一致性,适合频繁更新的项目。
缺点:
- 对于部分老旧或不常见的包,pnpm 的硬链接机制可能会导致兼容性问题。
- 相较于 npm 或 Yarn,pnpm 的社区支持较小(但在快速增长中)。
总结对比
| 特性 | Lerna | Turborepo | pnpm |
|---|---|---|---|
| 目标 | 多包管理与发布 | 高效构建与缓存 | 高效依赖管理 |
| 增量构建 | 无 | 有 | 无 |
| 缓存机制 | 无 | 高效缓存 | 高效安装缓存 |
| 性能优化 | 较差 | 极快 | 极快 |
| 适用场景 | 小型、中型项目 | 大型、复杂项目 | 高效依赖管理的项目 |
| 社区与支持 | 强大 | 新兴 | 快速增长 |
| 易用性 | 高 | 中等 | 高 |
基于以上选型对比,再加上对于pnpm更加熟悉,所以最终选择了pnpm作为构建工具。
实践过程
- 安装包管理工具
npm install -g pnpm
-
创建项目
通过
pnpm init创建package.json文件,在根目录下创建packages文件夹,在packages下创建mobile、pc、shared等项目文件夹,图片仅为示例
给各项目文件夹下创建package.json,修改package.json的name值,例如:@evaluation-ui/mop
- 配置workspace
packages:
- "packages/*"
最终项目结构
- 子包如何共享给别的包
之前我们对package.json中的name值进行了修改,用--workspace参数去安装共享子包,会去 workspace工作空间中找依赖项并安装
pnpm add @evaluation-ui/shared --workspace
在另一包中我们会在package.json文件中看到
"dependencies": {
"@evaluation-ui/shared": "workspace:^",
...
}
-
如何启动
可以在根目录的package.json下安装公共的依赖,在各自的项目中安装各自需要的依赖,可以在根目录下的package.json下配置启动命令
"scripts": {
"mop:dev": "pnpm -C ./packages/mop dev",
"mop:prod": "pnpm -C ./packages/mop build",
"sop:dev": "pnpm -C ./packages/sop dev",
"sop:prod": "pnpm -C ./packages/sop build"
},
-
构建与发布
现在基本都是CI/CD,可以在构建流程上给Jenkins设置环境变量,用于决定要编译构建哪个子包,
PROJECT_ENV就是我在jenkins构建的时候选择的环境变量
steps {
...
// 进入 项目文件夹并执行 pnpm install
sh "cd ./packages/${PROJECT_ENV}"
sh 'pnpm install'
...
sh "npm run ${PROJECT_ENV}:prod"
}
}
在docker build的时候也可以加上project参数
sh "docker buildx build --build-arg project=${PROJECT_ENV}"
在dockerfile里面可以去接收project参数,可以将编译出来的对应子包拷贝到nginx下
ARG project
# 拷贝打包的资源
...
ADD ./packages/${project}/dist /usr/local/nginx/html/${project}
总结
通过升级 Monorepo 架构,团队能够提高代码管理的效率,减少重复工作,确保代码质量,最终提升开发流程的整体生产力。但是也会存在一定的缺点,例如项目仓库会越来越大,构建速度可能会变慢,如有好的建议希望大家评论区指出来,一起讨论学习。