传统架构概述
什么是传统架构?
- 独立项目结构:每个项目作为独立的单元开发,维护和部署。
- 技术栈独立:不同的项目可能使用不同的技术栈和工具链。
- 依赖管理:每个项目都有自己独立的
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文件中添加以下内容: -
这将告诉
pnpm将packages目录下的所有子目录都作为工作区的一部分。
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-vue和my-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
- 运行结果:
全局公共样式
- 当多个项目都需要一样的样式,那么我们可以把这个样式抽离出来,定义在全局,定义一次,所有项目都可以直接使用
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-vue和my-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-vue和my-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-vue和my-project-vue-ts两个项目的任何地方使用common-styles中定义的样式了。