0%

lerna构建monorepo实战

初始化

创建空文件夹,然后运行:

1
npx lerna init

这行命令会创建一个空的packages文件夹,一个package.jsonlerna.json

1
2
3
4
|-- mono-repo
|-- packages
|-- lerna.json
|-- package.json

package.json有一点需要注意,他的private必须设置为true,因为本身并不是一个项目,而是承载多个子项目的项目,所以他自己不能直接发布,发布的应该是 packages/下面的各个子项目。

1
"private": true

lerna.json 初始化长这样:

1
2
3
4
{
"packages": ["packages/*"],
"version": "0.0.0"
}

packages字段就是标记你子项目的位置,默认就是packages/文件夹,他是一个数组,所以是支持多个不同位置的。

另外一个需要特别注意的是version字段,这个字段有两个类型的值,一个是像上面的0.0.0这样一个具体版本号,还可以是independent这个关键字。如果是具体版本号,那lerna管理的所有子项目都会有相同的版本号,如果你设置为independent,那各个子项目可以有自己的版本号。

因为我们的组件都是需要独立版本号,所以直接将version设置为independent

1
2
3
4
{
"packages": ["packages/*"],
"version": "independent"
}

创建子项目

创建子项目可以使用 lerna 的命令来创建:

1
lerna create <name>

通过 create 创建的子项目目录:

1
2
3
4
5
6
7
8
9
10
|-- mono-repo
|-- packages
|-- @mono-repo/project_1 # 推荐使用 `@<项目名>/<子项目名>` 的方式命名
|-- __test__
|-- lib
|-- @mono-repo/project_2
|-- __test__
|-- lib
|-- lerna.json
|-- package.json

这个是使用lerna create默认生成的目录结构,__test__文件夹下面放得是单元测试内容,lib下面放得是代码。实际使用过程中可以进行调整。

安装依赖项

lerna bootstrap

packages/下面的每个子项目有自己的依赖包,可使用命令:

1
2
3
lerna bootstrap

lerna bootstrap --hoist

删除已经安装的子项目node_modules可以手动删除,也可以使用:

1
lerna clean

具体命令含义可参考


yarn workspace

lerna bootstrap --hoist虽然可以将子项目的依赖提升到顶层,但是他的方式比较粗暴:先在每个子项目运行npm install,等所有依赖都安装好后,将他们移动到顶层的node_modules。这会导致一个问题,如果多个子项目依赖同一个第三方库,但是需求的版本不同怎么办?比如我们三个子项目都依赖 antd,但是他们的版本不完全一样:

1
2
3
4
5
6
7
8
// @mono-repo/project_1
"antd": "3.1.0"

// @mono-repo/project_2
"antd": "3.1.0"

// @mono-repo/project_3
"antd": "4.9.4"

这时候就需要介绍yarn workspace了,他可以解决前面说的版本不一致的问题,lerna bootstrap --hoist会把所有子项目用的最多的版本移动到顶层, 从而导致某些子项目依赖不正确,而yarn workspace 则会检查每个子项目里面依赖及其版本,如果版本不一样则会留在子项目自己的node_modules里面,只有完全一样的依赖才会提升到顶层。

还是以上面这个antd为例,使用yarn workspace的话,会把project1project2的 3.1.0 版本移动到顶层,而project3项目下会保留自己 4.9.4 的 antd,这样每个子项目都可以拿到自己需要的依赖了。

yarn workspace使用也很简单,yarn 1.0以上的版本默认就是开启workspace的,所以我们只需要在顶层的package.json加一个配置就行:

1
2
3
4
// 顶层package.json
{
"workspaces": ["packages/*"]
}

在 lerna.json 里面指定npmClientyarn,并将useWorkspaces设置为true,稍稍改动变成这样:

1
2
3
4
5
6
{
"packages": ["packages/*"],
"version": "independent",
"npmClient": "yarn",
"useWorkspaces": true
}

使用了yarn workspace,我们就不用lerna bootstrap来安装依赖了,而是像以前一样yarn install就行了,他会自动帮我们提升依赖,这里的yarn install无论在顶层运行还是在任意一个子项目运行效果都是一样的。

更多请参考

启动子项目

我们可以到子项目的目录运行 start 命令, 但是频繁切换文件是在太麻烦,lerna 提供了相应的命令以帮助我们直接在顶层运行:

1
lerna run [script]

比如我们在顶层运行了 lerna run start,这相当于去每个子项目下面都去执行 yarn run start 或者 npm run start,具体是 yarn 还是 npm,取决于你在 lerna.json 里面的这个设置:

1
"npmClient": "yarn"

如果我只想在其中一个子项目运行命令,应该怎么办呢?加上--scope就行了,比如我就在顶层的package.json里面加了这么一行命令:

1
2
3
4
5
6
// 顶层package.json
{
"scripts": {
"start:project1": "lerna --scope @mono-repo/project_1 run start"
}
}

注意scope后面的项目名称不是目录名,而是子项目package.jsonname

1
2
3
4
// 子项目project_1的package.json
{
"name": "@mono-repo/project_1"
}

引入公共组件

当我们的@mono-repo/project_2要引用@mono-repo/project_1的组件,我们需要先在@mono-repo/project_2package.json里面将依赖加上,我们可以去手动修改他,也可以使用lerna命令:

1
lerna add @mono-repo/project_1 --scope @mono-repo/project_2

这样我们可以在project2中引入project1的组件,但是需要注意多个项目引用时,要避免各个子项目之间的循环引用。

组件库相关请参考组件库构建与编写

发布

发布直接使用 lerna publish,因为此前我们已经将 version 修改为 independent,所以在发布时,只会自动更新有变动的子项目以及依赖该子项目的子项目的版本号。

更多发布命令参数及解释请参考lerna/publish