姚利锋
姚利锋
首页博客片段项目服务关于
☕
❤️
目录
  • 无目录
返回片段

发布于  2024 年 10 月 21 日,星期一

Monorepo和Pnpm共同使用

Monorepo架构允许将多个相关项目放在同一个代码仓库中,而Pnpm则通过其独特的依赖管理机制,解决了传统包管理器在Monorepo环境中可能遇到的依赖冗余和性能问题。博客内容可能涵盖了如何配置和使用Pnpm在Monorepo中进行依赖管理,以及这种组合如何帮助开发者更高效地组织和维护大型前端项目。

Monorepo 和 Pnpm

Monorepo 简介

  • Monorepo 是一种项目开发与策略管理的策略模式,它代表着 "单一代码仓库(Monolithic Repository)"

为什么项目需要使用 Monorepo?

  • 如果有多个项目,并且在多个项目中有着复用组件和复用逻辑。

使用 Monorepo 的好处

  • 代码复用:多个仓库都会用到的组件、工具函数、类型声明、样式等,可以放到 common 子包中,
  • 独立构建和部署:每个子包都是一个独立的项目,有自己的 package.json 文件,独立安装依赖、独立端口和本地启动、独立测试、独立构建和部署,互不影响
  • 降低切换成本:由于只有单一仓库,clone 代码、切换分支、安装依赖比较方便,不用在不同文件夹之间切换。
  • 节约磁盘空间:pnpm 天然具备 monorepo 能力,支持全局依赖管理,所有子包之间共享依赖,节约磁盘空间。
  • 方便提交 PR:由于是单仓库,增加新组件或给组件增加新特性,只需要提交一个 MR、编写一次 MR 描述、关联一次需求/缺陷单。
  • 方便代码检视:一个完整的特性只需要统一在一个 MR 中检视,不用在多个仓库/多个 MR 之间切换。
  • 灵活便于扩展:后续增加新的工程只需要在 packages 下增加一个子包,不需要申请新的代码仓库,也降低后续仓库维护成本,比如:配置保护分支 / GitHub Actions / 仓库标签等。

Monorepo (单仓多模块) 开发模式

  • 回归单体管理:它允许在一个代码仓库中管理多个项目,组件或服务,提供更好的代码共享和重用性。

  • 优点:

    • 保留了多仓库的主要优势
    • 管理所有项目,版本控制更容易一致,降低了不同项目之间的版本冲突
    • 可统一项目的构建和部署流程,降低了配置和维护多个项目需要的工作量
    • 代码复用
    • 模块独立管理
    • 分工明确,业务场景独立
    • 代码耦合度降低
  • 缺点

    • 随着时间推移,导致构建时间较长,管理起来较麻烦
    • 项目颗粒度的权限管理较为困难
      • 如何解决?
        • 使用代码所有权文件:使用如 CODEOWNERS 文件(Github 等平台支持)来指定某个目录和文件的所有者。当这些文件或目录被修改时,需要指定的所有者批准更改。
        • 分支策略:通过分支严格管理不同级别的开发人员可以访问和修改的分支,合并的权限。
        • 使用 web hooks:在对应的文件发生修改时,执行脚本来给所有者通知并检查。
        • 第三方工具:Gitlab 和 Bitbucket 提供了更细颗粒度的权限控制设置。
  • 为什么组件库项目会选用 Monorepo 模式?

    • components: 作为组件库的主要代码,实现各个 UI 组件的核心逻辑。
    • shared: 主要存放各种杂七杂八的工具方法。
    • theme: 实现组件库的主题样式定制方案。
    • cli: 实现组件库模板脚手架的命令行工具。
    • docs: 组件库的示例 demo 与使用文档。
    • playground: 组件库的在线编辑、演示应用。
      • S:模块划分的越清晰,复用时的灵活性、可操作性就越强,每个独立模块产物的体积也会越轻量。

Pnpm 在 Monorepo 中使用

  • 我们项目中有一个 main 应用,在 web 文件夹下还有一个 react 应用和 vue 应用,我们可以用 pnpm 对依赖进行统一管理
  1. 安装
# 安装npm i pnpm -g# 检验是否安装成功pnpm -v
复制代码
  1. 在根目录 pnpm 初始化生成 package.json
pnpm init
复制代码
  1. 配置工作空间, 新建 pnpm-workspace.yaml 文件
  2. 配置 pnpm-workspace.yaml 文件
  3. 安装项目依赖,在根目录运行如下命令,一键为所有项目安装依赖
pnpm i
复制代码

7. 暴露公用方法,创建 common 文件夹及 index.ts
8. 在 common 文件夹中运行 pnpm init 初始化

pnpm init
复制代码
  1. 在 pnpm-workspace.yaml 文件中添加 common 文件夹
复制代码
  1. 在 common 下编写 index.ts 文件暴露方法
export const hello = () => {    console.log("hello");};
复制代码
  1. 根目录运行 pnpm -F main-project add common 将 common 里的方法暴露给 main-project
  • 这里的-F 是--filter 的简写,用于过滤指定的 package,用法 pnpm --filter
pnpm -F main-project add common
复制代码
  1. 在 vue 和 react 项目页面中引入公共方法
  2. 启动项目
pnpm -F  main-project dev
复制代码

Pnpm 常用命令

#安装软件包及其依赖的任何软件包 如果workspace有配置会优先从workspace安装pnpm add <pkg>#安装项目所有依赖pnpm install#更新软件包的最新版本pnpm update#移除项目依赖pnpm remove#运行脚本pnpm run#创建一个 package.json 文件pnpm init#以一个树形结构输出所有的已安装package的版本及其依赖pnpm list
复制代码
  • 为指定模块安装外部依赖
    • 例如:为 A 包安装 lodash
    // 为 a 包安装 lodashpnpm --filter a i -S lodash // 生产依赖pnpm --filter a i -D lodash // 开发依赖
    复制代码
  • 指定内部模块之间的相互依赖
    • 例如:为 a 包安装内部依赖 b。
    // 指定 a 模块依赖于 b 模块pnpm --filter a i -S b
    复制代码
    • pnpm workspace 对内部依赖关系的表示不同于外部,它自己约定了一套 Workspace 协议 (workspace:)https://pnpm.io/zh/workspaces#workspace-%E5%8D%8F%E8%AE%AE-workspace。
    {    "name": "a",    // ...    "dependencies": {        "b": "workspace:^"    }}
    复制代码
    • 在实际发布 npm 包时,workspace:^ 会被替换成内部模块 b 的对应版本号(对应 package.json 中的 version 字段)。替换规律如下所示:
    {    "dependencies": {        "a": "workspace:*", // 固定版本依赖,被转换成 x.x.x        "b": "workspace:~", // minor 版本依赖,将被转换成 ~x.x.x        "c": "workspace:^" // major 版本依赖,将被转换成 ^x.x.x    }}
    复制代码

现有项目改造成 Monorepo 的步骤

  1. 创建子包
    • 第一步就是在根目录创建 packages 目录,增加项目子包,比如项目叫:portal
root├── packages|  └── portal|     ├── ... // 项目文件和目录
复制代码
  1. 现有项目文件放进子包里
    • 把现有工程的 src / public / package.json / vite.config.ts / tsconfig.xx.json / index.html / README.md 等项目启动和构建相关的目录和文件全部剪切到 packages/portal 目录中
root├── packages|  └── portal|     ├── index.html|     ├── package-lock.json|     ├── package.json|     ├── public|     ├── README.md|     ├── src|     ├── tsconfig.app.json|     ├── tsconfig.json|     ├── tsconfig.node.json|     └── vite.config.ts
复制代码
  1. 配置 pnpm-workspace.yaml
    • 根目录创建 pnpm-workspace.yaml 文件。
packages:    - packages/**
复制代码
  1. 配置 package.json
    • 项目原来的 package.json 属于子包,需要放到 portal 子包中。
    • 项目根目录需要创建一个新的 package.json 文件。
{  "name": "root",  "private": true}
复制代码
  1. 改造前后目录结构对比

  2. 验证本地启动和构建命令

执行 pnpm i 安装依赖执行 pnpm -F portal dev 本地启动执行 pnpm -F portal build 项目构建如果以上命令都正常,说明本次 Monorepo 改造成功!
复制代码
  1. 增加便捷命令
    • 将本地启动命令放到根目录的 packages.json scripts 中,方便启动。
{  "name": "root",  "private": true,+  "scripts": {+    "dev": "pnpm -F portal dev",+    "build": "pnpm -F portal build",+    "preview": "pnpm -F portal preview"+  }}
复制代码
  • 后续启动项目:pnpm dev
  • 构建项目:pnpm build
  1. 增加一个新项目 admin - 在 packages 目录下执行 npm create vite admin,选择 React 框架。
执行 pnpm i 安装依赖执行 pnpm -F admin dev 本地启动 admin 新项目执行 pnpm -F admin build 构建 admin 新项目
复制代码
  • 两个项目同时启动了。

  • 实现了 portal / admin 两个项目分开启动、分开构建、分开管理依赖、分开测试,互不影响,而且 portal 是 Vue 技术栈,admin 是 React 技术栈。

  • 可以在根目录的 package.json scripts 增加对应的便捷命令。

{  "name": "root",  "private": true,  "scripts": {    "dev": "pnpm -F portal dev",    "build": "pnpm -F portal build",    "preview": "pnpm -F portal preview",+    "dev:admin": "pnpm -F portal dev",+    "build:admin": "pnpm -F portal build",+    "preview:admin": "pnpm -F portal preview"  }}
复制代码
root├── package.json├── packages|  ├── admin|  |  ├── eslint.config.js|  |  ├── index.html|  |  ├── package.json|  |  ├── public|  |  ├── README.md|  |  ├── src|  |  ├── tsconfig.app.json|  |  ├── tsconfig.json|  |  ├── tsconfig.node.json|  |  └── vite.config.ts|  └── portal|     ├── index.html|     ├── package-lock.json|     ├── package.json|     ├── public|     ├── README.md|     ├── src|     ├── tsconfig.app.json|     ├── tsconfig.json|     ├── tsconfig.node.json|     └── vite.config.ts├── pnpm-lock.yaml└── pnpm-workspace.yaml
复制代码
  • 如果有些逻辑 portal / admin 都用到了,我们可以新加一个子包:common
  • 然后在 portal / admin 中引入 common。
pnpm -F portal i commonpnpm -F admin i common
复制代码
# 构建工具# 状态管理# 代码质量# 版本控制# DevOps
返回片段