基于pnpm对项目进行monorepo架构升级

668 阅读6分钟

需求描述

公司刚好准备对项目进行重构,老项目包含多端以及含多个子应用,其中有很多冗余的代码,甚至还有小伙伴来问某个子应用的仓库在哪。基于这个契机,对升级项目进行了评估,从而动手对老项目进行了框架和架构升级。从vue2升级vue3采用了 GoGoCode,架构上升级采用了monorepo。

f59242eb9d9fa144aaa4f89f1ce40c55.jpeg

什么是 Monorepo 架构

Monorepo(Mono Repository)架构 是一种将多个项目、模块或服务存储在一个单一代码仓库中的软件架构设计模式。在 Monorepo 架构下,多个子项目可以共享一个版本控制系统(如 Git),并在同一个仓库中进行管理、协作和部署。

这种架构常常用于需要跨多个模块进行高度协作的大型项目,尤其是当这些模块之间有共享代码、依赖关系或相似功能时。

为什么要升级到 Monorepo

  • 代码共享:可以方便地共享代码和模块。
  • 一致性:所有模块都遵循相同的开发和构建流程。
  • 版本控制:所有项目的版本可以统一管理。
  • 简化依赖:跨模块的依赖管理变得更简洁

构建工具的选择

1. Lerna

Lerna 是一种经典的 monorepo 工具,广泛用于管理多个 JavaScript 包的开发、发布、版本管理等。

主要特点:
  • 包管理与版本控制:通过 lerna bootstrap 命令连接和安装包的依赖,可以选择统一版本管理或独立版本管理。
  • 版本发布:Lerna 提供了自动化发布功能,支持发布多个包,并能管理版本号。
  • 支持依赖关系管理:通过 bootstrapping 操作链接本地包依赖,确保依赖关系的正确性。
优点:
  • 简单易用,适合中小型项目。
  • 支持独立的版本管理,可以为每个包定义不同的版本号。
  • 通过 lerna publish 进行批量发布,减少手动操作。
缺点:
  • 不直接优化构建性能,对大项目可能显得较慢,尤其是在多个包之间的依赖关系很复杂时。
  • 缺少内置的缓存机制和增量构建支持,随着项目规模的增长,性能瓶颈可能会显现。

2. Turborepo

Turborepo 是一个现代化的构建系统,专门优化了 monorepo 架构中的构建性能、缓存机制以及增量构建。它特别适合需要高效构建和任务并行化的项目。

主要特点:
  • 增量构建与缓存:通过智能缓存机制,避免重复构建,提高构建速度。
  • 并行执行任务:支持并行执行多个构建、测试、lint 等任务,从而提高效率。
  • 高效的构建流程:对常见的前端框架(如 React、Next.js)做了很好的集成,优化了构建过程。
优点:
  • 极高的构建速度,适用于大型项目和复杂的工作流。
  • 增量构建和任务缓存可以显著减少 CI/CD 时间。
  • 可以轻松扩展,支持复杂的工作流,适合多样化的开发团队。
  • 对 CI/CD 流程友好,节省云构建成本。
缺点:
  • 学习曲线较陡,配置和使用需要一定的理解。
  • 需要额外的工具和配置来处理包管理(如与 pnpm 一起使用)。
  • 目前对 monorepo 的支持较强,但在一些非标准化的场景中,可能需要一些调整。

3. pnpm

pnpm 是一个快速、节省空间的包管理工具,特别适合管理 monorepo 中的依赖。它优化了依赖管理的效率,并且支持快速安装和磁盘空间节省。

主要特点:
  • 硬链接优化:使用硬链接来共享依赖,避免重复下载,节省磁盘空间。
  • pnpm workspaces:通过 workspaces 来管理 monorepo 中的多个包,支持包之间的依赖和版本管理。
  • 快速安装与一致性:由于 pnpm 的缓存和安装机制非常高效,它的安装速度非常快,尤其适合依赖较多的项目。
优点:
  • 极快的安装速度,节省磁盘空间,适合大型 monorepo 项目。
  • 可以通过 pnpm-workspaces 管理多个包,避免了传统 npm 的依赖冲突问题。
  • 简化了包版本的管理和一致性,适合频繁更新的项目。
缺点:
  • 对于部分老旧或不常见的包,pnpm 的硬链接机制可能会导致兼容性问题。
  • 相较于 npm 或 Yarn,pnpm 的社区支持较小(但在快速增长中)。
总结对比
特性LernaTurborepopnpm
目标多包管理与发布高效构建与缓存高效依赖管理
增量构建
缓存机制高效缓存高效安装缓存
性能优化较差极快极快
适用场景小型、中型项目大型、复杂项目高效依赖管理的项目
社区与支持强大新兴快速增长
易用性中等

基于以上选型对比,再加上对于pnpm更加熟悉,所以最终选择了pnpm作为构建工具。

实践过程

  • 安装包管理工具
npm install -g pnpm
  • 创建项目

    通过 pnpm init 创建package.json文件,在根目录下创建packages文件夹,在packages下创建mobile、pc、shared等项目文件夹,图片仅为示例

image.png

给各项目文件夹下创建package.json,修改package.json的name值,例如:@evaluation-ui/mop

  • 配置workspace
packages:
  - "packages/*"

最终项目结构

image.png
  • 子包如何共享给别的包

之前我们对package.json中的name值进行了修改,用--workspace参数去安装共享子包,会去 workspace工作空间中找依赖项并安装

  pnpm add @evaluation-ui/shared --workspace

在另一包中我们会在package.json文件中看到

"dependencies": {
    "@evaluation-ui/shared": "workspace:^",
    ...
    }
  • 如何启动

    可以在根目录的package.json下安装公共的依赖,在各自的项目中安装各自需要的依赖,可以在根目录下的package.json下配置启动命令

"scripts": {
    "mop:dev": "pnpm -C ./packages/mop dev",
    "mop:prod": "pnpm -C ./packages/mop build",
    "sop:dev": "pnpm -C ./packages/sop dev",
    "sop:prod": "pnpm -C ./packages/sop build"
  },
  • 构建与发布

    现在基本都是CI/CD,可以在构建流程上给Jenkins设置环境变量,用于决定要编译构建哪个子包,PROJECT_ENV就是我在jenkins构建的时候选择的环境变量

 steps {
         ...
        // 进入 项目文件夹并执行 pnpm install
        sh "cd ./packages/${PROJECT_ENV}" 
        sh 'pnpm install'
        ...
        sh "npm run ${PROJECT_ENV}:prod"
      } 

    }

在docker build的时候也可以加上project参数

sh "docker buildx build --build-arg project=${PROJECT_ENV}"

在dockerfile里面可以去接收project参数,可以将编译出来的对应子包拷贝到nginx下

ARG project
# 拷贝打包的资源
...
ADD ./packages/${project}/dist /usr/local/nginx/html/${project}

总结

通过升级 Monorepo 架构,团队能够提高代码管理的效率,减少重复工作,确保代码质量,最终提升开发流程的整体生产力。但是也会存在一定的缺点,例如项目仓库会越来越大,构建速度可能会变慢,如有好的建议希望大家评论区指出来,一起讨论学习。

image.png