monorepo的认识和了解

monorepo 是一种将多个项目代码存储在一个仓库里的软件开发策略(mono 意为单一,repo 意为 仓库)。与之相对的是另一种流行的代码管理方式 MultiRepo,即每个项目对应一个单独的仓库来分散管理。


monorepo的优缺点

monorepo 有优亦有劣:

易于代码复用:所有项目代码集中于单一仓库,易于抽离出共用的业务组件或工具,并通过 TypeScript,Lerna 或其他工具进行代码内引用;
易于依赖管理:项目之间的引用路径内化于单一仓库,当某个项目的代码修改后,易于追踪其影响的是其他哪些项目。通过工具,易于版本依赖管理和版本号自动升级;并且所有的项目都是使用最新的代码,不会产生其它项目版本更新不及时的情况;
易于代码重构:代码重构难在不确定对某个项目的修改是否对于其他项目是破坏性的。而 monorepo 使得能够明确知道代码的影响范围,并且能够对被影响的项目可以进行统一的测试,利于不断优化代码;
倡导开放,透明,共享的组织文化,有利于开发者成长,代码质量的提升:monorepo 使得开发者都被鼓励去查看和修改他人的代码,同时,也会激起开发者维护代码,和编写单元测试的责任心(毕竟朋友来访之前,我们从不介意自己的房子究竟有多乱),这将会形成一种良性的技术氛围,从而保障整个组织的代码质量;
不易于项目粒度的权限管理;
额外的学习成本:monorepo 使得增加理清各个代码仓库之间的相互逻辑的成本;
需要工具链和自动构建工具的支持:项目若很庞大且没有工具链的支持,那么 git 管理、安装依赖、构建、部署会很麻烦和耗时。比如可以基于 Lerna、Yarn Workspaces 等工具更加自动化的处理依赖包之间的构建和发布。

在实际场景来落地项目级别的 Monorepo,需要一套完整的工程体系来进行支撑,因为基于 Monorepo 的项目管理,不只是代码放到一起,还需要考虑项目间依赖分析、依赖安装、构建流程、测试流程、CI 及发布流程等诸多工程环节,同时还要考虑项目规模到达一定程度后的性能问题,比如项目构建/测试时间过长需要进行增量构建/测试、按需执行 CI 等等,在实现全面工程化能力的同时,也需要兼顾到性能问题。
 

Monorepo 的使用

锁定环境:volta or nvm

除了使用 Docker 和显式的在文档中声明 node 和 npm(yarn)的版本之外,我们需要一个锁定环境的强力工具。相比使用 nvm,volta 支持当项目 CLI 工具与全局不兼容时,自动切换为项目指定的版本。

Volta 是用 Rust 构建的 JavaScript 工具管理器,它可以让我们轻松地在项目中锁定 node,npm(yarn) 的版本。只需在安装完 Volta 后,在项目的根目录中执行 volta pin 命令,那么无论您当前使用的 node 或 npm(yarn)版本是什么,volta 都会自动切换为您指定的版本。
 

volta pin [email protected]
volta pin [email protected]
 
/**
"volta": {
  "node": "12.20.2",
  "yarn": "1.19.2"
}
*/

用 workspace 特性复用 package

workspace:

  1. 避免重复安装包,减少了磁盘空间的占用,并降低了构建时间;
  2. 内部代码可以彼此相互引用;

考虑到支持 workspace 的 npm 版本目前不是 LTS,所以选择 yarn 作为包管理工具,而且需要完成如下工作:

1. 调整目录结构,将相互关联的项目放置在同一个目录,推荐命名为 packages

2. 在项目根目录里的 package.json 文件中,设置 workspaces 属性,属性值为之前创建的目录(packages);

 3. 为了避免我们误操作将仓库发布,在 package.json 文件中,设置 private 属性为 true。

然后,在项目根目录中执行 npm install 或 yarn install 后,项目根目录中生成由所有子项目共用的 npm 包和我们自己的子项目共同构成的 node_modules 目录,正因如此,才使得可以像引入一般的 npm 模块一样彼此相互引用。

统一 ESlint / Typescript / babel 配置

可以在 项目根目录设置通用的 tsconfig.base.json / .eslintrc / .babelrc 配置,然后在子项目的对应配置文件中声明继承 extend 属性即可:

// babel
{
  "extends": "../../.babelrc"
}
 
// eslint
{
  "extends": "../../.eslintrc",
  "parserOptions": {
    "project": "tsconfig.json"
  }
}
 
// typescript
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true, // 用于帮助 TypeScript 快速确定引用工程的输出文件位置
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

统一脚本 script 配置

每个子项目的 package.json 文件中的 scripts 属性都大同小异,我们可以使用 scripty 管理脚本命令提高复用性,简单来说,scripty 允许将脚本命令定义在文件中,并在 package.json 文件中直接通过文件名来引用。这使我们可以实现如下目的:

子项目间复用脚本命令;
像写代码一样编写脚本命令,无论它有多复杂,而在调用时,像调用函数一样调用;
同时,将脚本分为两类 package 类与 workspace 类,分别放在两个文件夹内:

利用 lerna 统一包管理 

lerna 可以帮助解决 monorepo 因为多个子项目放在一个代码仓库,并且子项目之间又相互依赖时带来的两个棘手问题:

在多个子目录执行相同的命令时需要手动进入各个目录,并执行命令;

子项目更新后只能手动追踪依赖该项目的其他子项目,并升级其版本。

首先,在项目根目录使用 npx lerna init 初始化,根目录会生成一个 lerna.json 文件,稍作改动:

{
  "packages": ["packages/*"],
  "npmClient": "yarn", // 显式声明包客户端为 yarn
  "version": "independent",  // 将每个子项目的版本号看作是相互独立的
  "useWorkspaces": true, // 开启 workspaces 特性:子项目引用和通用包提升
}

当某个子项目代码更新后,运行 lerna publish 时,Lerna 将监听到代码变化的子项目并以交互式 CLI 方式让开发者决定需要升级的版本号,关联的子项目版本号不会自动升级,反之,当我们填入固定的版本号时,则任一子项目的代码变动,都会导致所有子项目的版本号基于当前指定的版本号升级。

Lerna 有很多有用的 CLI 命令:

lerna bootstrap:等同于 lerna link + yarn install,用于创建符合链接并安装依赖包;
lerna run:会像执行一个 for 循环一样,在所有子项目中执行 npm script 脚本,并且,它会非常智能的识别依赖关系,并从根依赖开始执行命令;
lerna exec:像 lerna run 一样,会按照依赖顺序执行命令,不同的是,它可以执行任何命令,例如 shell 脚本;
lerna publish:发布代码有变动的 package,因此首先需要在使用 Lerna 前使用 git commit 命令提交代码,好让 Lerna 有一个 baseline;
lerna add:将本地或远程的包作为依赖添加至当前的 monorepo 仓库中,该命令让 Lerna 可以识别并追踪包之间的依赖关系,因此非常重要;比如# 向 @mono/project2 和 @mono/project3 中添加 @mono/project1:

lerna add @mono/project1 '@mono/project{2,3}'


lerna 中有用的参数:

--concurrency <number>:参数可以使 Lerna 利用计算机上的多个核心,并发运行,从而提升构建速度;
--scope '@mono/{pkg1,pkg2}':--scope 参数可以指定 Lerna 命令的运行环境,通过使用该参数,Lerna 将不再是一把梭的在所有仓库中执行命令,而是可以精准地在我们所指定的仓库中执行命令,并且还支持示例中的模版语法;
--stream:该参数可使我们查看 Lerna 运行时的命令执行信息
结合 verdanccio 在本地创建 npm 代理仓库,先发布体验和验证,全局安装 npm install --global verdaccio,在项目根目录创建 .npmrc 文件,并在文件中将 npm 仓库地址改写为本地代理地址 registry="http://localhost:4873/",执行 shell 命令 verdaccio 后,访问  localhost:4837即可,这样,每当执行 lerna publish 时,子项目所构建成的 package 将会先发布在本地 npm 仓库中,只有执行 lerna bootstrap 时,verdaccio 才发布到远程 npm。

猜你喜欢

转载自blog.csdn.net/irisMoon06/article/details/130053745