场景
维护多个仓库的公共代码是一件头疼的事情,每次对公共代码的改动都要全量仓库同步,最后决定用 monorepo 改造一番。
Monorepo
Monorepo(monolithic repository) 是管理项目代码的一个方式,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个 repo。
目前不少大型开源项目采用了这种方式,如 Babel
、React
、Vue
等。monorepo 管理代码只要搭建一套脚手架,就能管理(构建、测试、发布)多个 package。
在项目的第一级目录的内容以脚手架为主,主要内容都在 packages
目录中、分多个 package 进行管理。目录结构大致如下:
├── packages
| ├── pkg1
| | ├── package.json
| ├── pkg2
| | ├── package.json
├── package.json
此时会有一个问题,虽然拆分子 npm 包管理项目简单了很多,但是当仓库内容有关联时,调试变得困难。所以理想的开发环境应该是只关心业务代码,可以直接跨业务复用而不关心复用方式,调试时所有代码都在源码中。
目前最常见的 monorepo 解决方案是 lerna 和 yarn 的 workspaces 特性。用 yarn 处理依赖问题,lerna处理发布问题。
Lerna
Lerna是npm模块的管理工具,为项目提供了集中管理package的目录模式,如统一的 repo 依赖安装、package scripts和发版等特性。
安装
建议全局安装
npm i -g lerna
初始化项目
lerna init
初始化后,会生成 packages 空目录和 package.json 和 lerna.json 配置文件,配置文件如下:
//package.json
{
"name": "root",
"private": true, // 私有的,不会被发布,是管理整个项目,与要发布的npm包解耦
"devDependencies": {
"lerna": "^3.22.1"
}
}
//lerna.json
{
"packages": [
"packages/*"
],
"version": "0.0.0"
}
创建npm包
执行命令后可修改包信息,这里创建 @monorepo/components 和 @monorepo/utils
lerna create @monorepo/components
安装依赖
lerna和yarn workspace安装依赖的方法都鸡肋
lerna add lodash // 为所有 package 增加 lodash 模块
// 为 @monorepo/utils 增加 lodash 模块(lodash可替换为内部模块,如@monorepo/components)
lerna add lodash --scope @monorepo/utils
lerna add的鸡肋之处是一次只能安装一个包…
依赖包管理
一般情况下 package 的依赖都是在各自的 node_modules
目录下,这不仅增加了包的安装和管理成本,还可能会出现同一个依赖有多个的情况。所以可把所有 package 的依赖包都提升到工程根目录。
lerna 和 yarn workspace都可以把依赖包提升到 repo 根目录管理。lerna 在安装依赖时(lerna bootstrap
)提供了--hoist
选项,但其鸡肋的地方在于,由于 lerna 直接以字符串对比 dependency 的版本号,同一个依赖在版本号完全相同时才会提升到根目录下,举个栗子:
A 依赖了 @babel/core@^7.10.0
B 依赖了 @babel/core@^7.11.4
lerna 会在 A 的 node_modules
目录下安装 7.11.4 版本的 babel/core
,并在 A 目录下生成了一份 package-lock.json,这无疑加大了维护成本和包的体积。
而yarn workspace在这种情况下只会在根目录有一份yarn-lock.json,也不会重复在子目录下安装依赖。
yarn workspace
搭建环境
主要是为安装依赖的配置