基于pnpm monorepo 项目工程化设计

424 阅读8分钟

传统架构概述

什么是传统架构?

  • 独立项目结构:每个项目作为独立的单元开发,维护和部署。
  • 技术栈独立:不同的项目可能使用不同的技术栈和工具链。
  • 依赖管理:每个项目都有自己独立的 node_modules 和依赖配置。
  • 部署策略:各自独立部署和上线,通常依赖 CI/CD 工具进行自动化部署。

传统架构的优缺点

优点

  • 开发独立性:各团队或者项目相互隔离,独立开发,互不干扰。
  • 灵活性:可以自由选择和切换技术栈和工具链。
  • 简单性:每个项目都有自己独立的依赖和配置,不需要与其他项目进行复杂的依赖管理。

缺点

  • 代码重复性:多个项目中可能存在重复代码或者模块,难以统一管理和复用。
  • 依赖版本冲突:多个项目维护相同依赖的不同版本,可能导致版本冲突和兼容性问题。
  • 协作成本高:跨项目的联调和功能共享需要额外的沟通和管理。
  • 构建效率低:每个项目都需要单独构建和部署,效率低下。

PNPM Monorepo 架构概述

monorepo 架构

  • 混合项目结构,所有相关的工程形成子包进行管理。
  • 技术栈高度统一,团队基建项目、业务项目、子服务、技术栈
  • 规范化、自动化、流程化项目之间共享
  • 依赖管理,版本统一管理
  • 部署策略,docker、compose、自动化脚本统一部署流程

pnpm 的优势

  • 链接机制(软链接)
  • 缓存机制, 寻址
  • 原生支持 workspace
  • 依赖扁平化管理
  • 磁盘占用少
  • 速度快

用一句话总结:中心化思想解决依赖复用问题

一、什么是Monorepo 定义与概念

Monorepo 单个仓库多项目管理,是一种项目代码管理方式, 将多个项目或模块放在一个仓库中进行管理,这种方式有助于代码共享、依赖管理、版本控制、构建和部署等方面的复杂性,并提供更好的可重用和协作性。

需求背景

在大型项目中,通常会有多个子项目或模块,这些子项目或模块之间可能存在依赖关系,如果每个子项目或模块都单独管理,那么在依赖管理和版本控制方面可能会变得非常复杂。而Monorepo通过将所有子项目或模块放在一个仓库中进行管理,可以简化依赖管理和版本控制,提高开发效率。

1.多项目整合与独立运行:

  • 将多个项目整合在一个仓库中,方便管理和维护。
  • 提供灵活的运行机制,允许一次运行多个项目或者运行其中的某一个项目。

2.共享样式与组件:

  • 提取多个项目中共有的样式和组件,避免重复编写代码,降低维护的成本, 将他们放置在 workspace的根目录下。
  • 通过 pnpm workspace 的依赖管理机制,使得其他项目可以方便地引用这些共享的样式和组件。

实现步骤

一、多项目搭建-配置-运行

1.创建项目目录
mkdir my-monorepo
cd my-monorepo
2.初始化项目
  • 执行 pnpm init 来初始化一个 package.json的文件,这个文件将作为整个 Monorepo 目录的配置文件。
pnpm init
  • 初始化完成后,会在 my-monorepo 目录下生成一个 package.json 文件。
{
  "name": "my-monorepo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.9.0"
}
3. 配置 package.json 的文件
  • 删除 package.json 文件中的 main、test 字段,因为 Monorepo 中通常不需要这个字段。
  • 添加 private 字段,将其设置为 true,以防止意外发布到 npm 上。
{
  "name": "my-monorepo",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.9.0"
}
4. 创建 pnpm-workspace.yaml 文件
  • my-monorepo 目录下创建一个 pnpm-workspace.yaml 文件,这个文件将用于配置 Monorepo 的工作区。
touch pnpm-workspace.yaml
  • pnpm-workspace.yaml 文件中添加以下内容:

  • 这将告诉 pnpmpackages 目录下的所有子目录都作为工作区的一部分。

packages:
  # 这样写相当于可以访问packages目录下的所有子目录
  - "packages/*"
5. 创建目录 packages 这个文件用来放你的项目,可以放一个或者多个
  • 这里的目录名字可以自定义,但通常使用 packages 作为目录名。
  • 但是无论自定义什么名字,在 pnpm-workspace.yaml 文件中都需要进行相应的配置。
  • 譬如:你的名字叫 my-workspace,那么在 pnpm-workspace.yaml 文件中就需要这样写:
packages:
  - "my-workspace/*"
6. 创建子项目
  • packages 目录下创建一个子项目,例如 my-project-vue
pnpm  create  vite my-project-vue  --template  vue
  • 再创建一个子项目 my-project-vue-ts
pnpm  create  vite my-project-vue-ts  --template  vue-ts
  • 创建成功后的目录结构如下:
my-monorepo
├── pnpm-workspace.yaml
├── packages
│   ├── my-project-vue
│   └── my-project-vue-ts
└── package.json
7. 避免端口冲突,设置子项目启动端口
  • 分别在 my-project-vuemy-project-vue-ts 两个项目目录下的 vite.config.js/vite.config.ts 中,设置不同的端口,避免端口冲突。
server: {
  port: 8080,
}
8. 在 my-monorepo 目录下安装依赖
pnpm  i
  • 安装完成依赖后的目录结构如下:
my-monorepo
├── node_modules
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── packages
│   ├── my-project-vue
│   └── my-project-vue-ts
└── package.json
9. 在根目录下的 package.json 文件中添加脚本(用于运行子项目的命令)
  • my-monorepo 目录下的 package.json 文件中添加以下脚本:
  • -C 参数用于指定要运行的子项目目录。
{
  "scripts": {
    "dev:vue": "pnpm  run  -C packages/my-project-vue dev",
    "dev:vue-ts": "pnpm  run  -C packages/my-project-vue-ts dev",
    "build:vue": "pnpm  run  -C packages/my-project-vue build",
    "build:vue-ts": "pnpm  run  -C packages/my-project-vue-ts build"
  }
}
10. 运行启动项目
  • 在运行的命令都配置好了,结果运行会报错,因为 pnpm 还没有识别到 Monorepo 的配置,需要重新安装依赖。 注意: 要再安装一遍依赖

    pnpm  i
    
  • 运行 pnpm run dev:vue 启动 my-project-vue 项目。

  • 到目前两个项目都可以启动和运行了。

11. 配置: 一个命令启动所有项目
  • my-monorepo 目录下的 package.json 文件中
  • 我们已经为各个子项目配置了启动命令和构建命令,现在我们希望有一个命令可以同时启动所有子项目。
  • 如果想要一次性启动所有子项目的开发服务器
  • 可以使用一个能够并行运行多个任务的工具,比如 npm-run-all 或者 concurrently。 这里使用 concurrently 来实现这个需求
11.1 需要安装 concurrently
pnpm  i  concurrently  -D
11.2 在根目录下的 package.json 文件中添加脚本
"scripts": {
  "dev:all": "concurrently \"pnpm run -C packages/my-project-vue dev\" \"pnpm run -C packages/my-project-vue-ts dev\""
}

注意,这里我们使用了双引号来包围每个  pnpm run  命令,并且整个  concurrently  命令也被双引号包围。这是因为在 JSON 中,字符串内部的特殊字符(如空格)需要被转义,而使用双引号是最简单的方法。此外,由于 Windows 命令提示符对引号的处理有所不同,这种方法在 Unix/Linux/macOS 和 Windows 的 Git Bash 或 PowerShell 中都应该能正常工作。

11.3 运行命令
pnpm dev:all
  • 运行结果: c3ca2024-f332-4665-ad9f-d14db8006f58.png

全局公共样式

  • 当多个项目都需要一样的样式,那么我们可以把这个样式抽离出来,定义在全局,定义一次,所有项目都可以直接使用
1. 在 pnpm-workspace.yaml 配置公共样式
packages:
  # 这样写相当于可以访问packages目录下的所有子目录
  - "packages/*"
  - "common/*"
2. 在 my-monorepo 根目录下创建 common 文件夹,并在 common 文件夹下创建 common-styles 文件夹,并在 common-styles 文件夹下创建 index.css 文件
.common-button {
  background-color: #6b9cd0;
  color: white;
  border: none;
  padding: 10px 20px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
}

.common-button:hover {
  background-color: #0056b3;
}
  • 文件结构如下:
my-monorepo
├── common
│   └── common-styles
│       └── index.css
├── pnpm-workspace.yaml
├── packages
│   ├── my-project-vue
│   └── my-project-vue-ts
└── package.json
3.初始化 common-styles 包
  • cd 到 common/common-styles 目录下,初始化 common-styles
  • 运行 pnpm init 初始化一个 package.json文件
pnpm init -y
{
  "name": "common-styles",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.9.0"
}
  • 文件结构如下:
my-monorepo
├── common
│   ├── styles
│   │   ├── index.css
│   │   └── package.json
├── pnpm-workspace.yaml
├── packages
│   ├── my-project-vue
│   └── my-project-vue-ts
└── package.json
4. 在项目的 package.json 中添加依赖
  • my-project-vuemy-project-vue-ts 两个项目的 package.json 文件中添加依赖
  • dependencies 部分添加对 common-styles 的依赖。由于 common-styles 是一个本地包,需要使用文件路径来指定它的位置。
  • 譬如: 在项目 my-project-vue 中添加依赖
{
  "dependencies": {
    "vue": "^3.5.13",
    "common-styles": "file:../../common/common-styles"
  }
}

注意:这里的路径是相对路径,它指向项目根目录下的 common/common-styles 文件夹。如果你的项目结构不同,请调整路径以匹配你的实际情况。

5. 安装依赖
  • 回到 my-project-vue 和 ``my-project-vue-ts两个项目的根目录下,运行pnpm i` 命令来安装依赖。
6. 在项目中引入公共样式
  • my-project-vuemy-project-vue-ts 两个项目的入口文件中引入公共样式。
import { createApp } from "vue";
import "./style.css";
import "common-styles/index.css";
import App from "./App.vue";

createApp(App).mount("#app");

注意:由于使用的相对路径依赖,并且没有将 common-styles 发布到 npm 仓库,因此不需要(也不能)在 node_modules 中找到它。Vite 或你的构建工具应该能够解析这个本地依赖并正确地引入样式文件。

7. 使用全局样式
  • 现在,你可以在 my-project-vuemy-project-vue-ts 两个项目的任何地方使用 common-styles 中定义的样式了。

1297b243-da76-4386-8651-75e42f2c928f.png