Skip to content
目录

项目集成之Monorepo

Monorepo(单一代码仓库)是指在软件开发中,将多个项目或组件放置在同一个代码仓库中的开发模式。在传统的多仓库开发模式中,每个项目或组件都有自己独立的代码仓库,而在Monorepo中,所有项目或组件共享同一个代码仓库

发展历程

  • 单体应用:应用都处在一个仓库内,按文件名等划分一些公共方法、组件
  • 多仓库:多个仓库管理一个应用,每个仓库都是一个独立的包
  • 单仓库多模块:单个仓库管理多个包

单体应用、到仓库会随着业务的复杂度提升、项目的复杂度增长,会导致编译速度变慢、调试成本变大、代码共享难度大等一系列的问题,这些问题monorepo模式都可以解决

概念

什么是monorepo呢❓

Monorepo 只是一个管理概念,实际上它并不代表某项具体的技术,更不是所谓的框架。开发人员需要根据不同场景、不同的研发习惯,使用相应的技术手段或者工具,来达到或者完善它的整个流程,从而达到更好的开发和管理体验

monorepo中可以管理多个不同的包,通常结构如下所示:

sh
group
├── node_modules/
├── packages/
   ├── packageA  # 子包A
       └── package.json
   └── packageB  # 子包B
        └── package.json
└── package.json

# 项目中引入
npm install @group/packageA ...

作用

  1. 代码共享和复用:Monorepo将多个项目或组件放置在同一个代码仓库中,促进了代码的共享和复用。不同项目之间可以更轻松地共享代码、模块、工具和配置文件,避免了重复编写和维护相似的代码。这样可以减少代码冗余,提高开发效率
  2. 简化协作和协同开发:Monorepo使团队成员能够更紧密地协作开发。开发人员可以在同一个代码库中共享问题和解决方案,跟踪彼此的更改,更容易进行代码评审和知识共享。这样可以提高团队的协同效率,促进团队合作和沟通
  3. 统一构建和部署过程:Monorepo可以简化构建和部署过程。由于所有项目或组件在同一个代码仓库中,可以使用统一的构建和部署流程来管理整个代码库。这样可以减少维护多个独立仓库的复杂性,提高开发效率和一致性。同时,可以更轻松地管理共享的构建工具和配置文件
  4. 更好的代码可视性和管理:Monorepo提供了更好的代码可视性和管理。所有项目或组件的代码都在同一个代码仓库中,可以方便地查看整个代码库的历史记录、版本控制和问题跟踪。这使得追踪更改、维护和管理代码更加方便。同时,可以更容易地进行代码重构、重组和重命名,因为所有相关代码都在同一个仓库中

任何技术或多或少都有一些缺陷,monorepo模式中代码对于成员都是透明的,无法精确按文件夹进行权限分配;除此之外子包变多时对于版本的控制也会变得麻烦

包管理方案

上面讲了关于monorepo单仓库多包管理方案的优势和必要性,那么具体是如何实现的呢?接下来我们就来看看通过如下几个方式实现monorepo

npm

npm v7开始引入了workspace的概念,不过使用的效果并不是很好,也不推荐使用

sh
 npm install npm@7
 npm init -w packages

在package.json中配置:

json
{
  "name": "monorepo",
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

创建子包:

sh
 cd packages && npm init -w packages/a

依赖安装:

sh
 npm install axios -w package/a

执行命令:

sh
npm run dev -w package/a

由于npm无法提高依赖的重复使用,磁盘占用空间会变得越来越大,一般不使用npm

yarn

初始化仓库:

sh
 yarn init -y

定义workspaces字段,是个数组可以使用 glob 语法,下面就定义了packages路径下的为子包

json
{
  "name": "root",
  "private": true,
  "license": "MIT",
  "workspaces": ["packages/*"]
}

根路径安装依赖:

sh
 yarn add typescript -W -D

-W表示在当前根路径安装

给某个子包安装依赖:

sh
 yarn workspace pkg1 add vue -D

给某个子包安装其他的子包:

sh
 yarn workspace pkg1 add pkg2@1.0.0

需要注意安装子包时需要标明对应的工作区的版本号,否则会报错

通常yarn monorepo会结合 lerna 来管理仓库,它是有babel为解决monorepo而创建的,内置了丰富的版本管理、打包管理流程,现在yarn已经具备了workspace的能力,因此lerna可以专注用来管理monorepo的版本发布,而包的管理交给yarn处理就可以了。现在来看下使用:

sh
 npm install lerna -g
# 初始化
 npx lerna init

初始化后会生成一个配置文件:

json
{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "1.0.0",
}

更多lerna的配置可以查看官方文档

lerna简单的使用命令

  • run:使用lerna run build就会执行所有包的build的命令,通过--scope参数可以指定对应的包
  • create: 使用lerna create 包名会创建对应的子包

使用lerna平滑升级包

sh
 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

lerna目前结合了 nx 来进行增量打包,性能可以说特别好

pnpm

pnpm就是为解决monorepo问题而产生的的包管理方案,结合了很多其他包管理器优秀的方案

首先安装pnpm:

sh
 npm i pnpm -g

初始化项目:

sh
 pnpm init

创建pnpm-workspace.yaml文件使用yaml语法定义所有的子包,同样支持glob匹配

yaml
packages:
  - 'packages/**'
  - 'docs'

这里规定packages下的路径和docs都是子包

pnpm命令使用

  • install:使用pnpm install会安装所有依赖
  • add:使用pnpm add --filter 子包名 依赖名来为指定的子包安装依赖

更多命令使用请参考官方文档

构建方案

在monorepo项目中打包可能会是很难受的,对于一个大的项目来讲可能会有很多子包,子包之间可能也会存在复杂的依赖关系,那么打包的顺序就很关键了,如果手动来管理就会变得非常麻烦,所以就出现了一些优秀的打包器:nxturborush

turbo

turbo是Vercel公司推出的一款打包器,他可以实现增量打包并对产物实现缓存的逻辑,如果源文件的内容没变时就会直接跳过打包流程使用缓存,这里我们使用pnpm结合turbo一起使用

sh
# 在工作区安装turbo
 pnpm add turbo -Dw

创建turbo配置文件:

json
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": { // 
      "dependsOn": ["^build"] // 
    },
  },
  "globalDependencies": [] // 
}

nx

nx是Nrwl推出的专门为Angular和React等现代Web应用提供工具和解决方案的公司

sh
# 使用nx创建monorepo仓库
 npx create-nx-workspace

会生成nx.json配置文件:

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"
      ]
    },
  }
}

上面的cacheableOperations属性用来配置可以被缓存的脚本,可以自行定义;targetDefaults来配置打包的流程,每个key对应一个打包脚本,dependsOn中定义当前脚本依赖哪个脚本,以^开头的表示依赖的其他包的脚本,这样打包时就会有个先后顺序;没有的则表示当前仓库的执行脚本。outputs表示打包后的产物路径,nx会根据上次的产物进行计算,对源码没有改变的包会进行增量打包

rush

rush是微软公司推出的monorepo仓库的解决方案

sh
 npm install -g @microsoft/rush

版本方案

changesets

https://github.com/changesets/changesets

changesets用来解决monorepo仓库的包版本管理方案

实战

Released under the MIT License.