monorepo

使用 lerna 和 yarn 构建 monorepo 项目

Posted by huangqing on May 10, 2021

使用 lernayarn 构建 monorepo 项目,核心思想是 用 yarn 来处理依赖问题,用 lerna 来处理发布问题。

Monorepo

Monorepo(monolithic repository) 是管理项目代码的一个方式,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个 repo

  • 多个项目的代码放在在同一存储库中这种开发策略称之为 Monorepo
  • lerna Babel 开发用来管理多包的工具,基于 Monorepo 理念在工具端的实现
  • yarn Facebook 贡献的 Javascript 包管理器
  • commitlint 用来规范git commit信息
├── packages
|   ├── pkg1
|   |   ├── package.json
|   ├── pkg2
|   |   ├── package.json
├── package.json

monorepo 最主要的好处是统一的工作流和 Code Sharing。比如我想看一个 pacakge 的代码、了解某段逻辑,不需要找它的 repo,直接就在当前 repo;当某个需求要修改多个 pacakge 时,不需要分别到各自的 repo 进行修改、测试、发版或者 npm link,直接在当前 repo 修改,统一测试、统一发版。只要搭建一套脚手架,就能管理(构建、测试、发布)多个 package。 但是 repo 的体积较大。因为各个 package 理论上都是独立的,所以每个 package 都维护着自己的 dependencies,而很大的可能性,package 之间有不少相同的依赖,而这就可能使 install 时出现重复安装。

实际开发中的场景,「Monorepo」的使用通常是通过 yarnworkspaces 工作空间,又或者是 lerna 这种第三方工具库来实现。使用「Monorepo」的方式来管理项目会给我们带来以下这些好处:

  • 只需要一个仓库,就可以便捷地管理多个项目。
  • 可以管理不同项目中的相同第三方依赖,做到依赖的同步更新。
  • 可以使用其他项目中的代码,清晰地建立起项目间的依赖关系。

「Vue3」正是采用的 yarn 的 workspaces 工作空间的方式管理整个项目:

vue2 monorepo

Yarn Workspace

Yarn 是一个软件包管理器,还可以作为项目管理工具。无论你是小型项目还是大型单体仓库(monorepos)。

Yarn 从 1.0 开始支持 Workspace。这个功能的主要作用是让多个项目共用一个 node_modules 目录,提升开发效率和降低磁盘空间占用。

yarn 突出的是对依赖的管理,包括 packages 的相互依赖、packages 对第三方的依赖,yarn 会以 semver 约定来分析 dependencies 的版本,安装依赖时更快、占用体积更小;但欠缺了「统一工作流」方面的实现。

配置

├── packages
|   ├── workspace-a
|   |   ├── package.json
|   ├── workspace-b
|   |   ├── package.json
├── package.json

package.json

{
  "private": true,
  "workspaces": ["workspace-a", "workspace-b"]
}

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

workspace-a/package.json:

{
  "name": "workspace-a",
  "version": "1.0.0",

  "dependencies": {
    "cross-env": "5.0.5"
  }
}

workspace-b/package.json:

{
  "name": "workspace-b",
  "version": "1.0.0",

  "dependencies": {
    "cross-env": "5.0.5",
    "workspace-a": "1.0.0"
  }
}

搭建环境

普通项目:clone下来后通过yarn install,即可搭建完项目,有时需要配合postinstall hooks,来进行自动编译,或者其他设置。

monorepo: 各个库之间存在依赖,如A依赖于B,因此我们通常需要将B link到A的node_module里,一旦仓库很多的话,手动的管理这些link操作负担很大,因此需要自动化的link操作,按照拓扑排序将各个依赖进行link

解决方式:通过使用workspaceyarn install会自动的帮忙解决安装和link问题

yarn install 
# 等价于 
lerna bootstrap --npm-client yarn --use-workspaces

清理环境

在依赖乱掉或者工程混乱的情况下,清理依赖

普通项目: 直接删除node_modules以及编译后的产物。

monorepo: 不仅需要删除root的node_modules的编译产物还需要删除各个package里的node_modules以及编译产物

解决方式:使用lerna clean来删除所有的node_modules,使用yarn workspaces run clean来执行所有package的清理工作

lerna clean # 清理所有的node_modules
yarn workspaces run clean # 执行所有package的clean操作

安装|删除依赖

安装工程依赖包:在主工程上执行一次即可

yarn install

执行 pagkages 中的项目

yarn workspace <workspaceName> <commandName> ...

例如:

yarn workspace workspace-a run dev

使用 packages 中的工程包:

workspace-b -> package.json

  "dependencies": {
     "workspace-a": "1.0.0"
  }

查看依赖树关系:

yarn workspaces info

安装/删除依赖:

注意:执行安装之前,删除主目录下的 node_modules,并删除 packages 目录下全部的 node_modules

普通项目: 通过yarn addyarn remove即可简单姐解决依赖库的安装和删除问题

monorepo: 一般分为三种场景

给某个package安装依赖:

yarn workspace packageB add packageA 

将packageA作为packageB的依赖进行安装

给所有的package安装依赖: 使用yarn workspaces add lodash 给所有的package安装依赖

给root 安装依赖:一般的公用的开发工具都是安装在root里,如typescript,我们使用yarn add -W -D typescript来给root安装依赖

对应的三种场景删除依赖如下

yarn workspace packageB remove packageA yarn workspaces remove lodash yarn remove -W -D typescript

安装完依赖文件结构如下:

yarn-workspace-files

Lerna

举例

monorepo 的形式来管理这个仓库。

由于他们使用了相同的技术栈,那么 eslintprettier ,甚至 webpack 配置都可以提取到最外面,不用维护在每个项目里面。

以 create-react-app peject 之后的配置为例:

- node_modules
  - react
  - react dom
  - redux
  - lodash
- packages
  - project1
    - package.json
  - project2
    - package.json
- config
  - webpack.config.js
  - webpack.dev.config.js
- scripts
  - create.js
  - bin.js
  - build.js
  - start.js
- .eslintrc.js
- .prettierrc
- commitlint.config.js
- jest.config.js
- tsconfig.js
- package.json
- yarn.lock

我们可以看到,通用配置都被提取到了最外层。

参考资料