Skip to content

Monorepo

Monorepo 并不是什么新技术,而是一种多包管理方案,一种思维,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用,如 Google、Facebook 和 Microsoft 等。

Monorepo 演进

阶段一:单仓库巨石应用, 一个 Git 仓库维护着项目代码,随着迭代业务复杂度的提升,项目代码会变得越来越多,越来越复杂,大量代码构建效率也会降低,最终导致了单体巨石应用,这种代码管理方式称之为 Monolith。

阶段二:多仓库多模块应用, 于是将项目拆解成多个业务模块,并在多个 Git 仓库管理,模块解耦,降低了巨石应用的复杂度,每个模块都可以独立编码、测试、发版,代码管理变得简化,构建效率也得以提升,这种代码管理方式称之为 MultiRepo。

阶段三:单仓库多模块应用, 随着业务复杂度的提升,模块仓库越来越多,MultiRepo这种方式虽然从业务上解耦了,但增加了项目工程管理的难度,随着模块仓库达到一定数量级,会有几个问题:跨仓库代码难共享;分散在单仓库的模块依赖管理复杂(底层模块升级后,其他上层依赖需要及时更新,否则有问题);增加了构建耗时。于是将多个项目集成到一个仓库下,共享工程配置,同时又快捷地共享模块代码,成为趋势,这种代码管理方式称之为 MonoRepo

monorepo

Monorepo 的优势

  • 多项目代码都在一个仓库中,相同版本依赖提升到顶层只安装一次,节省磁盘内存。
  • 依赖调试方便,依赖包迭代场景下,借助工具自动 npm link,直接使用最新版本依赖,简化了操作流程。
  • 多项目在一个仓库,工程配置一致,代码质量标准及风格也很容易一致。
  • 构建性 Monorepo 工具可以配置依赖项目的构建优先级,可以实现一次命令完成所有的部署。

Monorepo 项目搭建

想要搭建 Monorepo 项目关键在于两个点:

  1. 如何把包提取到顶层
  2. 如何让 A 项目 引入 B 项目

这就得借助 npm 的工作空间(pnpm,cnpm 等同理)

pnpm workspace

pnpm 内置了对单一存储库(也称为多包存储库、多项目存储库或单体存储库)的支持, 你可以创建一个 workspace 以将多个项目合并到一个仓库中。

我们只需要在主目录下创建一个pnpm-workspace.yaml 文件即可开启pnpm的工作空间功能。并配置代码如下:

yml
packages:
  - packages
  - docx

其配置意思是把主目录下的 packages 和 docx 当成一个单独的项目。如果是后面代 * 号,例如

yml
packages:
  - packages/*

那么说明把 packages 目录下的每个目录都当成一个单独的项目

TIP

需要注意的是,pnpm 并不是通过目录名称,而是通过目录下 package.json 文件的 name 字段来识别仓库内的包与模块的。

使用 Monorepo

接下来,我们创建 packages 和 docx 目录,这时整个项目目录如下,并初始化 pnpm init 各个文件下的 packages 文件.

monorepo

然后我们尝试一下在主目录下安装 lodash

sh
pnpm install -w D lodash

注意: -w 选项代表在 monorepo 模式下的根目录进行操作。代表每个子包都能访问根目录的依赖 然后我们进入 packages 子包创建 main.js 来测试一下

js
// packages/main.js
import _ from 'lodash'

console.log(_.chunk(['a', 'b', 'c', 'd'], 2)) // [ [ 'a', 'b' ], [ 'c', 'd' ] ]

执行 node main.js 后,能顺利输出 [ [ 'a', 'b' ], [ 'c', 'd' ] ],说明子包的 lodash 已经是从主包中获取。

TIP

Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. (Use node --trace-warnings ... to show where the warning was created)

如果出现这个报错,只需要在 packages.json 加上 "type": "module" 即可。

Monorepo 相关指令

-w

-w 选项代表在 monorepo 模式下的根目录进行操作。

例如在根目录下安装模块

sh
pnpm install -w D lodash

--filter

--filter 选项用于指定操作应用的范围或目标。

例如安装 子项目a 独属的模块

sh
pnpm install D lodash --filter a

或者与 -w 结合,指定 子项目a 安装依赖根目录某个模块

sh
pnpm install D -w lodash --filter a

或者 指定 子项目a 依赖于 子项目 b

sh
pnpm --filter a i -S b

然后我们就可以在 子项目a 的 packages.json 下看到

json
{
    "name": "a",
    // ...
    "dependencies": {
        "b": "workspace:^"
    }
}

在实际发布 npm 包时,workspace:^ 会被替换成内部模块 b 的对应版本号(对应 package.json 中的 version 字段)。替换规律如下所示:

json
{
    "dependencies": {
        "a": "workspace:*", // 固定版本依赖,被转换成 x.x.x
        "b": "workspace:~", // minor 版本依赖,将被转换成 ~x.x.x
        "c": "workspace:^"  // major 版本依赖,将被转换成 ^x.x.x
    }
}

本文参考了:
带你了解更全面的 Monorepo - 优劣、踩坑、选型
从 0 到 1 搭建 Vue 组件库框架