monorepo
monorepo
什么是 monorepo
monorepo这个词是meno和repo两个缩写词的组合,meno是更少的意思,repo是repository的缩写,也就是仓库,合起来就是使用更少的仓库来进行版本管理控制,将所有项目都放在一个代码仓库进行管理。
截止到目前,主流的前端开源项目基本全部使用monorepo,例如react,vue,babel等。
为什么要使用 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

版本控制
lerna提供两种版本控制的方式:
- 一种是所有项目固定使用一个版本号,位于
monorepo内部的lerna.json内部指定的version,这样就会导致执行lerna publish会更新所有package的版本号; - 另一种是使用
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引导的packagepackages:指定lerna package目录,默认就是packages文件夹useWorkspaces:是否使用yarn workspace,如果开启需要在仓库根目录下的package.json指定workspaces
命令行
lerna init
创建基于lerna的monorepo仓库,或者更新当前仓库的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]
在包含指定script的package内部执行该命令。
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的局限性,yarn从0.28版本(目前 1.22)以后支持monorepo管理package之间的依赖。
并且从yarn-1.0版本以后,yarn默认支持,这种方式在yarn内部称为workspace。使用上来说,只需要在
monorepo根目录下的package.json中注册workspace的文件夹即可。
{
"private": true,
"workspaces": ["packages/*"]
}
yarn workspace不能取代lerna,主要是为了解决lerna的问题,因此lerna从2.0.0版本以后,支持使用lerna bootstrap --use-workspace来使用yarn引导monorepo依赖的管理和创建,这样monorepo内部所有package的依赖都只会安装在仓库根目录的node_modules下。

命令行
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。
