项目集成之Monorepo
Monorepo(单一代码仓库)是指在软件开发中,将多个项目或组件放置在同一个代码仓库中的开发模式。在传统的多仓库开发模式中,每个项目或组件都有自己独立的代码仓库,而在Monorepo中,所有项目或组件共享同一个代码仓库
发展历程
- 单体应用:应用都处在一个仓库内,按文件名等划分一些公共方法、组件
- 多仓库:多个仓库管理一个应用,每个仓库都是一个独立的包
- 单仓库多模块:单个仓库管理多个包
单体应用、到仓库会随着业务的复杂度提升、项目的复杂度增长,会导致编译速度变慢、调试成本变大、代码共享难度大等一系列的问题,这些问题monorepo模式都可以解决
概念
什么是monorepo呢❓
Monorepo 只是一个管理概念,实际上它并不代表某项具体的技术,更不是所谓的框架。开发人员需要根据不同场景、不同的研发习惯,使用相应的技术手段或者工具,来达到或者完善它的整个流程,从而达到更好的开发和管理体验
monorepo中可以管理多个不同的包,通常结构如下所示:
group
├── node_modules/
├── packages/
│ ├── packageA # 子包A
│ │ └── package.json
│ └── packageB # 子包B
│ └── package.json
└── package.json
# 项目中引入
npm install @group/packageA ...
2
3
4
5
6
7
8
9
10
11
作用
- 代码共享和复用:Monorepo将多个项目或组件放置在同一个代码仓库中,促进了代码的共享和复用。不同项目之间可以更轻松地共享代码、模块、工具和配置文件,避免了重复编写和维护相似的代码。这样可以减少代码冗余,提高开发效率
- 简化协作和协同开发:Monorepo使团队成员能够更紧密地协作开发。开发人员可以在同一个代码库中共享问题和解决方案,跟踪彼此的更改,更容易进行代码评审和知识共享。这样可以提高团队的协同效率,促进团队合作和沟通
- 统一构建和部署过程:Monorepo可以简化构建和部署过程。由于所有项目或组件在同一个代码仓库中,可以使用统一的构建和部署流程来管理整个代码库。这样可以减少维护多个独立仓库的复杂性,提高开发效率和一致性。同时,可以更轻松地管理共享的构建工具和配置文件
- 更好的代码可视性和管理:Monorepo提供了更好的代码可视性和管理。所有项目或组件的代码都在同一个代码仓库中,可以方便地查看整个代码库的历史记录、版本控制和问题跟踪。这使得追踪更改、维护和管理代码更加方便。同时,可以更容易地进行代码重构、重组和重命名,因为所有相关代码都在同一个仓库中
任何技术或多或少都有一些缺陷,monorepo模式中代码对于成员都是透明的,无法精确按文件夹进行权限分配;除此之外子包变多时对于版本的控制也会变得麻烦
包管理方案
上面讲了关于monorepo单仓库多包管理方案的优势和必要性,那么具体是如何实现的呢?接下来我们就来看看通过如下几个方式实现monorepo
npm
npm v7开始引入了workspace的概念,不过使用的效果并不是很好,也不推荐使用
➜ npm install npm@7
➜ npm init -w packages
2
在package.json中配置:
{
"name": "monorepo",
"private": true,
"workspaces": [
"packages/*"
]
}
2
3
4
5
6
7
创建子包:
➜ cd packages && npm init -w packages/a
依赖安装:
➜ npm install axios -w package/a
执行命令:
npm run dev -w package/a
由于npm无法提高依赖的重复使用,磁盘占用空间会变得越来越大,一般不使用npm
yarn
初始化仓库:
➜ yarn init -y
定义workspaces字段,是个数组可以使用 glob 语法,下面就定义了packages
路径下的为子包
{
"name": "root",
"private": true,
"license": "MIT",
"workspaces": ["packages/*"]
}
2
3
4
5
6
根路径安装依赖:
➜ yarn add typescript -W -D
-W
表示在当前根路径安装
给某个子包安装依赖:
➜ yarn workspace pkg1 add vue -D
给某个子包安装其他的子包:
➜ yarn workspace pkg1 add pkg2@1.0.0
需要注意安装子包时需要标明对应的工作区的版本号,否则会报错
通常yarn monorepo会结合 lerna 来管理仓库,它是有babel为解决monorepo而创建的,内置了丰富的版本管理、打包管理流程,现在yarn已经具备了workspace的能力,因此lerna可以专注用来管理monorepo的版本发布,而包的管理交给yarn处理就可以了。现在来看下使用:
➜ npm install lerna -g
# 初始化
➜ npx lerna init
2
3
初始化后会生成一个配置文件:
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "1.0.0",
}
2
3
4
更多lerna的配置可以查看官方文档
lerna简单的使用命令
- run:使用
lerna run build
就会执行所有包的build的命令,通过--scope
参数可以指定对应的包 - create: 使用
lerna create 包名
会创建对应的子包
使用lerna平滑升级包
➜ lerna version --no-private
info cli using local version of lerna
lerna notice cli v7.1.5
lerna info versioning independent
lerna info Assuming all packages changed
? Select a new version for @mono/pkg1 (currently 0.0.1) (Use arrow keys)
❯ Patch (0.0.2)
Minor (0.1.0)
Major (1.0.0)
Prepatch (0.0.2-alpha.0)
Preminor (0.1.0-alpha.0)
Premajor (1.0.0-alpha.0)
Custom Prerelease
Custom Version
2
3
4
5
6
7
8
9
10
11
12
13
14
lerna目前结合了 nx 来进行增量打包,性能可以说特别好
pnpm
pnpm就是为解决monorepo问题而产生的的包管理方案,结合了很多其他包管理器优秀的方案
首先安装pnpm:
➜ npm i pnpm -g
初始化项目:
➜ pnpm init
创建pnpm-workspace.yaml
文件使用yaml语法定义所有的子包,同样支持glob匹配
packages:
- 'packages/**'
- 'docs'
2
3
这里规定packages
下的路径和docs都是子包
pnpm命令使用
- install:使用
pnpm install
会安装所有依赖 - add:使用
pnpm add --filter 子包名 依赖名
来为指定的子包安装依赖
更多命令使用请参考官方文档
构建方案
在monorepo项目中打包可能会是很难受的,对于一个大的项目来讲可能会有很多子包,子包之间可能也会存在复杂的依赖关系,那么打包的顺序就很关键了,如果手动来管理就会变得非常麻烦,所以就出现了一些优秀的打包器:nx、turbo、rush
turbo
turbo是Vercel公司推出的一款打包器,他可以实现增量打包并对产物实现缓存的逻辑,如果源文件的内容没变时就会直接跳过打包流程使用缓存,这里我们使用pnpm结合turbo一起使用
# 在工作区安装turbo
➜ pnpm add turbo -Dw
2
创建turbo配置文件:
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": { //
"dependsOn": ["^build"] //
},
},
"globalDependencies": [] //
}
2
3
4
5
6
7
8
9
nx
nx是Nrwl推出的专门为Angular和React等现代Web应用提供工具和解决方案的公司
# 使用nx创建monorepo仓库
➜ npx create-nx-workspace
2
会生成nx.json
配置文件:
{
"extends": "nx/presets/npm.json",
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e"
],
}
}
},
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"{projectRoot}/dist"
]
},
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
上面的cacheableOperations
属性用来配置可以被缓存的脚本,可以自行定义;targetDefaults
来配置打包的流程,每个key对应一个打包脚本,dependsOn
中定义当前脚本依赖哪个脚本,以^
开头的表示依赖的其他包的脚本,这样打包时就会有个先后顺序;没有的则表示当前仓库的执行脚本。outputs
表示打包后的产物路径,nx会根据上次的产物进行计算,对源码没有改变的包会进行增量打包
rush
rush是微软公司推出的monorepo仓库的解决方案
➜ npm install -g @microsoft/rush
版本方案
changesets
https://github.com/changesets/changesets
changesets用来解决monorepo仓库的包版本管理方案