模式演变

项目开发与管理模式在很大程度上影响着团队效率和项目的可维护性,随着软件工程的发展,逐渐出现了以下三种管理模式,单体架构(Monolithic),多库架构(Multirepo),和单库架构(Monorepo)。

IMG_5058

单体架构(Monolithic)

单体架构是指将整个应用程序的所有功能和模块打包成一个独立的可执行文件或部署单元。所有的代码、资源和配置文件都集中在一个项目和一个代码库中。

优点
  1. 简化开发和部署:由于所有代码在一个单一的代码库中,开发和部署过程非常直观。
  2. 较低的管理复杂度:没有不同代码库之间的依赖和版本管理问题。
  3. 统一的构建过程:构建和测试过程可以较为集中和统一,减少环境配置的复杂度。
  4. 易于集成和测试:因为所有模块都在一个代码库中,集成测试和端到端测试相对简单。
缺点
  1. 可扩展性差:随着应用程序增长,单体架构会变得庞大和难以维护。
  2. 灵活性受限:更新某一个小模块可能需要重新测试和部署整个应用。
  3. 团队协作受限:多个团队同时修改同一代码库,容易引起冲突和代码合并问题。
  4. 技术债务累积:技术堆栈僵化,难以引入新的技术和工具。

多库架构(Multirepo)

多库架构是将一个大型项目拆分为多个独立的代码库,每个库负责一个特定的功能模块。这些模块通过接口或API进行交互。

优点
  1. 模块化:项目被拆分成不同的组件,各自独立管理,便于维护和升级。
  2. 提高灵活性:可以独立更新某个模块,而不影响其他模块。
  3. 增强团队合作:不同团队可以独立开发各自负责的模块,减少代码冲突。
  4. 优化构建时间:因为构建过程可以只针对改动过的模块,适用性强。
缺点
  1. 依赖管理复杂:模块之间的依赖关系多,管理起来比较复杂。
  2. 版本控制难度大:需要严格控制版本兼容性,否则容易出现问题。
  3. 配置重复:每个库都需要独立的配置文件和构建脚本,增加了一定的重复工作。
  4. 集成测试复杂:需要额外的集成测试步骤,确保各个模块能够正确协同工作。

单库架构(Monorepo)

单库架构是将所有项目的代码和资源集中放在一个代码库中,通常通过目录结构进行模块化管理。

优点
  1. 统一管理:所有代码和配置集中管理,方便追溯和审计。
  2. 减少依赖管理:版本控制和依赖管理相对简单,各模块之间可以共享依赖。
  3. 增强协作:代码库共享,促进团队之间的协作和代码重用。
  4. 一致的环境:一个代码库可以更好地保持各个模块之间的一致性。
缺点
  1. 构建和测试复杂:单个代码库的构建和测试时间可能较长,需要优化工具来分布式构建和测试。
  2. 初始设置复杂:需要更多的初期配置和工具链支持。
  3. 代码库庞大:随着越来越多的模块和代码加入,代码库过于庞大,可能影响性能。

pnpm 实现 Monorepo

为什么选择 pnpm?

  • 由于 pnpm 能够复用之前下载的包,并且优化了并行下载和安装的机制,它的安装速度远快于传统的 npm 和 yarn;
  • pnpm 的严格节点模块结构防止了幽灵依赖的出现;
  • pnpm 原生支持 monorepo 工作区。通过 pnpm workspaces,可以统一管理和协调 monorepo 中的多个子项目。

pnpm 支持的Monorepo 的机制是 Workspace,需要在项目根目录通过 pnpm-workspace.yaml 文件声明相互独立的工作目录(需要提前初始化 npm),现在主流的前端工具都是用的这种模式,比如Vue、Vite等,可以在 github 看下对应的源码目录。

pnpm-workspace.yaml 中需要声明一个 packages 数组,内容是独立工作空间的目录地址,比如

packages:
  - docs
  - packages/*

这个声明文件会将 docs 目录和 packages 下的所有子目录标记为独立工作空间。现在我们创建以下的目录结构

├── docs
├── packages
│   ├── core
│   │   └── package.json
│   └── utils
│       └── package.json
├── package.json
└── pnpm-workspace.yaml

如果要添加所有子库都使用的公共依赖,可以使用以下命令将依赖添加到根目录的声明文件中

$ pnpm i vite vitest -D -w

前面的参数都是很常见的 pnpm 使用方法,后面的 -w--workspace-root 的简写,作用是将依赖安装到所有子应用。

如果想要针对某个子应用安装依赖可以有两种方法:

  1. 导航到子应用目录下,执行安装命令
  2. 使用filter参数指定子应用安装,pnpm i chalk -D --filter @monorepo/utils,filter 后面的是要安装依赖的子应用package.json中的name。

如果是项目中的子库互相引用,同样是上面两种形式,只是把要安装的依赖换成子库的package.json中的name字段,pnpm 会自动转为 "@monorepo/utils": "workspace:^" 这种格式,会在发布时自动替换为具体的版本号。

发布

monorepo 的发布是一个很复杂的任务,pnpm 目前还没有内置工作流,可以使用一些第三方的库来完成,比如 changesets

  1. 安装 changesets,使用 changesets 可以方便地管理和发布版本更新。
pnpm add @changesets/cli -w
npx changeset init

这将在项目根目录中创建一个 .changeset 目录。

  1. 创建并写入 changeset,当有更改需要发布时,可以创建一个新的 changeset。
npx changeset

按照提示进行操作,选择有哪些包需要更新,以及更新的类型(patch, minor, major)。完成后,在 .changeset 目录中会生成相应的 changeset 文件。

  1. 发布 changeset,需要执行 changeset 的版本更新。
npx changeset version

这将根据 .changeset 文件中的内容,自动更新各个子包的版本号,并生成更新日志。

  1. 发布到 NPM,如果已经登录了 npm,并拥有相应的权限,可以使用以下命令将所有包发布到 npm。
pnpm publish -r

前端小白