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
引导的package
packages
:指定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
。