跳到主要内容

monorepo

· 阅读需 17 分钟
Oxygen

monorepo

什么是 monorepo

monorepo这个词是menorepo两个缩写词的组合,meno是更少的意思,reporepository的缩写,也就是仓库,合起来就是使用更少的仓库来进行版本管理控制,将所有项目都放在一个代码仓库进行管理。

截止到目前,主流的前端开源项目基本全部使用monorepo,例如reactvuebabel等。

为什么要使用 monorepo

使用monorepo有以下好处:

  • 代码复用更简单,类似的功能或通信协议可以抽象到共享库中,而不需要依赖包管理器;
  • 原子级别的提交使得不同项目版本管理更加方便,例如babel内部包含@babel/parser@babel/traverse等许多子项目,这些模块同时修改都在一个仓库代码内,修改完一次提交就行了;
  • 避免重复安装第三方依赖,在大型项目中肯定会使用大量第三方依赖包,这些包的版本管理和安装需要不断重复进行,而monorepo内部只需要管理和安装一次,其他package都可以共享;
  • 跨团队协作更加方便,由于代码都在一个仓库内部,所以基本没有权限限制,大家都可以参与和维护

不过,使用monorepo也有一些不便:

  • package划分的粒度不好控制,这一般取决于架构的水平
  • 随着不同项目的进行,整个monorepo会越来越大
  • 权限控制没有multirepo那样精细到项目
  • 团队协作导致的修改可能因为沟通不及时的问题而影响很多面,不过这都是开发者不规范使用的问题

lerna

lerna 是一个工具,它优化了使用 git 和 npm 管理多包存储库的工作流。

lerna架构中,每个单独的项目都是放在package目录的子文件夹下进行管理,每个package内部都有管理自身配置项的package.json文件,依赖也都会安装在各个package文件夹内的node_modules内部。

使用

使用lerna需要全局安装 CLI 工具

yarn global add lerna

lerna init

lerna bootstrap

lerna publish

image-20211212143303117

版本控制

lerna提供两种版本控制的方式:

  1. 一种是所有项目固定使用一个版本号,位于monorepo内部的lerna.json内部指定的version,这样就会导致执行lerna publish会更新所有package的版本号;
  2. 另一种是使用lerna init --independent创建的项目,可以独立管理每个package.json内部的版本号,每次执行lerna publish的时候,可以单独选择每个修改过的package内部的版本号。

配置项

lerna.json中可以指定以下配置项:

{
"version": "1.1.3",
"npmClient": "npm",
"command": {
"publish": {
"ignoreChanges": ["ignored-file", "*.md"],
"message": "chore(release): publish",
"registry": "https://npm.pkg.github.com"
},
"bootstrap": {
"ignore": "component-*",
"npmClientArgs": ["--no-package-lock"]
}
},
"packages": ["packages/*"]
}
  • version:版本号,指定为independent表示每个package单独管理
  • npmClient:使用npm还是yarn安装依赖
  • command.publish.ignoreChanges:忽略以某些文件修改导致的发布更新,使用glob模式
  • command.publish.message:执行发布的提交信息
  • command.publish.registry:发布源
  • command.bootstrap.ignore:取消引导安装依赖的文件
  • command.bootstrap.npmClientArgs:执行lerna bootstrap时传递给npm install或者yarn install命令的参数
  • command.bootstrap.scope:限制lerna bootstrap引导的package
  • packages:指定lerna package目录,默认就是packages文件夹
  • useWorkspaces:是否使用yarn workspace,如果开启需要在仓库根目录下的package.json指定workspaces

命令行

lerna init

创建基于lernamonorepo仓库,或者更新当前仓库的lerna版本。

lerna bootstrap

安装所有package的依赖项。lerna为项目内的每个package执行npm install或者yarn install,然后在这些包之间创建symlink,以相互引用。

例如babel项目中babel-core依赖@babel/generator,而且它们都在同一个monorepo中,那么在packages/babel-core下安装的@babel/generator会通过symlink链接到同级的@babel/generator目录。

symlink是一类特殊的文件,包含一条以绝对路径或者相对路径形式指向其他文件或者目录的引用。目前 Unix,Windows 都支持symlink,一个符号链接文件仅包含有一个文本字符串,其被操作系统解释为一条指向另一个文件或者目录的路径。

lerna bootstrap --use-workspaces使用yarn workspace的形式管理packages的依赖。

lerna add

lerna add <package> --scope=[package] --dev

lerna add用于安装依赖到指定package内部,如果没有指定scope,那么默认安装到所有package内部。

注意不同package之间如果相互依赖,也需要通过lerna add安装,这样lerna才能创建symlink关联到依赖的package内部。

lerna publish

发布所有更新过内容的package仓库到npm,会提示version的更新。

lerna run [script]

在包含指定scriptpackage内部执行该命令。

lerna 的局限性

lerna作为package管理的容器,lerna无法做到高效地管理node_modules

  • lerna为每个package单独执行xxx install的操作,并且依赖之间无法共享,所以会导致依赖的重复安装,可以参考上文使用lerna创建的项目内部管理react的安装项。
  • lerna必须通过执行lerna bootstrap才能为不同package创建symlink,而如果单独在package内部执行yarn install等命令就会导致symlink被破坏,因此package管理非常受限制。

yarn workspace

为了解决lerna的局限性,yarn0.28版本(目前 1.22)以后支持monorepo管理package之间的依赖。

并且从yarn-1.0版本以后,yarn默认支持,这种方式在yarn内部称为workspace。使用上来说,只需要在

monorepo根目录下的package.json中注册workspace的文件夹即可。

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

yarn workspace不能取代lerna,主要是为了解决lerna的问题,因此lerna2.0.0版本以后,支持使用lerna bootstrap --use-workspace来使用yarn引导monorepo依赖的管理和创建,这样monorepo内部所有package的依赖都只会安装在仓库根目录的node_modules下。

image-20211212164417532

命令行

yarn install

安装依赖

yarn workspace

yarn workspace <workspace_name> <command>

yarn workspace命令用于在指定package内部执行命令,类似于lerna add

例如安装依赖到指定package内部:

yarn workspace package1 add react react-dom --D

yarn workspaces

yarn workspaces run <command>

在每个packages内部执行命令,类似于lerna run