一、前言
1. pnpm
pnpm 是前端包管理工具之一,常见的还有npm、yarn等,但pnpm由于安装速度快、磁盘占用低、内置支持monorepo等优势,所以更推荐使用。
1-1 包管理工具核心特点对比
特性 | pnpm | npm | yarn |
---|---|---|---|
安装速度 | 🚀 快(基于硬链接和缓存) | ⏳ 较慢(全量复制) | 🚀 快(全量复制+缓存优化) |
磁盘占用 | 📉 低(共享 node_modules 依赖) |
📈 高(每个项目完整安装) | 📈 高(和 npm 类似) |
包管理机制 | 硬链接+全局存储 | 直接安装到 node_modules |
直接安装到 node_modules |
严格性 | 🔒 严格模式(默认不可创建隐式依赖) | 🔓 允许隐式依赖 | 🔓 允许隐式依赖 |
Monorepo 支持 | ✅ 内置(pnpm workspaces ) |
❌ 需要 npm workspaces |
✅ yarn workspaces |
离线安装 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
自动并行安装 | ✅ 默认并行安装 | ❌ 串行安装(npm 6 )✅ 并行安装(npm 7+ ) |
✅ 并行安装 |
Hoisting(提升依赖) | 🚫 默认不提升(更符合依赖层级) | ✅ 允许 | ✅ 允许 |
Lockfile | pnpm-lock.yaml |
package-lock.json |
yarn.lock |
1-2 简单命令对比
操作 | pnpm | npm | yarn |
---|---|---|---|
安装依赖 | pnpm install |
npm install |
yarn install |
添加依赖 | pnpm add <pkg> |
npm install <pkg> |
yarn add <pkg> |
移除依赖 | pnpm remove <pkg> |
npm uninstall <pkg> |
yarn remove <pkg> |
运行脚本 | pnpm run <script> |
npm run <script> |
yarn <script> |
全局安装 | pnpm add -g <pkg> |
npm install -g <pkg> |
yarn global add <pkg> |
初始化项目 | pnpm init |
npm init |
yarn init |
2. monorepo
2-1 概念
所有模块在同一仓库下,代码共享和协作更方便,无需发布到 npm 才能使用。
2-2 优势
代码共享更容易:
- 所有模块在同一仓库下,代码共享和协作更方便,无需发布到 npm 才能使用。
一致的依赖管理:
- 可以使用 pnpm/yarn/npm workspaces 统一管理依赖,避免每个模块单独安装,减少重复安装的依赖包。
更容易的跨模块修改:
- 在此之前,多采用 Multi-repo 模式,即多个项目分别单独管理,这会导致一些问题,如修改 A 项目 API 需要:
- 更新 A 项目代码
- 发布新版本到 npm
- 更新 B 项目的依赖
- 在 Monorepo 中,只需在同一个仓库内修改 A 和 B,避免复杂的版本管理。
- 在此之前,多采用 Multi-repo 模式,即多个项目分别单独管理,这会导致一些问题,如修改 A 项目 API 需要:
简化 CI/CD 流程:
- 只需配置一个 CI/CD 流程,即可管理多个模块的构建、测试和发布。
原子性提交:
- 允许一次 PR 修改多个模块,保证所有变更一次性生效,避免版本不兼容问题。
2-3 缺点
- 首次配置繁琐
- Git 仓库规模较大,所有项目都在一个仓库中,随着时间推移,Git 历史可能变得庞大。
- 不方便对项目做权限划分
2-4 Monorepo vs Multi-repo
对比项 | Monorepo(单仓库) | Multi-repo(多仓库) |
---|---|---|
代码存储 | 单个 Git 仓库 | 每个模块独立 Git 仓库 |
依赖管理 | 共享 node_modules,统一管理 | 每个模块单独管理 |
版本管理 | 可以使用 workspace:* |
每个模块单独发布版本 |
跨模块修改 | 统一 PR 提交,改动同步 | 需要更新多个仓库,发布新版本 |
CI/CD | 统一 CI/CD 流程 | 每个仓库需要独立 CI/CD |
适用场景 | 内部共享库、组件库、前端+后端一体化 | 需要独立发布、独立管理的项目 |
二、实现流程
1. 构建项目结构
根目录下执行 pnpm init
初始化项目
使用pnpm进行work-space,根据 pnpm官方文档,项目根目录下创建 pnpm-workspace.yaml,基本内容如下:
packages:
# 指定根目录直接子目录中的包
- 'my-app'
# packages/ 直接子目录中的所有包 - 所有前端项目存储的目录
- 'packages/*'
# components/ 子目录中的所有包 - 给所有项目使用的公共组件
- 'components/**'
# utils/ 子目录中的所有包 - 给所有项目使用的公共工具库
- 'utils/**'
# 排除测试目录中的包
- '!**/test/**'
根目录下创建 .npmrc,用于确保后续下载依赖时,优先从本地查找,而非远程npm。
# 解决pnpm add时优先在本地查找依赖
link-workspace-packages=true
根据 pnpm-workspace.yaml 中声明的目录创建结构,此时项目目录如下:
2. 测试本地依赖
通过 utils、packages/web 测试本地依赖
2-1 utils
在 utils目录 下,执行pnpm init
,此时出现 package.json,修改name为 @xxx/utils 格式,并加入 "private": true,
{
"name": "@tbc/utils",
"version": "1.0.0",
"description": "",
"main": "index.js",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {}
}
utils目录下创建 text.js
const testFn1 = () => { console.log("fn1"); }
const testFn2 = () => { console.log("fn2"); }
export default {
testFn1,
testFn2
}
utils目录下创建 index.js,注意:
文件名要与 package.json 中的 main 字段一致,默认为 index.js
index.js 中整合所有方法并导出
export * from "./test"
2-2 packages/web
packages目录下,创建项目 web 模拟真实项目,执行npm create vite
后pnpm i
执行pnpm add @tbc/utils
安装 utils目录的依赖
npm run dev
启动项目,并在 App.vue
中使用 utils下 的公共方法
import { testFn1 } from '@tbc/utils'
console.log(testFn1(),"===");
查看控制台打印
此时直接修改 utils目录 下的公共方法(加入return)
web项目中无需更新依赖,自动使用最新公共方法,查看打印
3. 组件库
3-1 封装简易组件
组件库与utils同理,在components目录下创建组件库项目,如npm create vite
。修改 package.json 的name字段为 @tbc/ui。
组件库项目根目录下,创建 dir 目录,用于存储组件。
dir/MyButton.vue
<template>
<button style="background-color: antiquewhite;width: 150px;border-radius: 8px;">自定义组件</button>
</template>
组件库项目根目录下,创建 index.js 文件,用于对外暴露组件
index.js
import 'element-plus/dist/index.css';
import MyButton from "./dir/MyButton.vue"
export { MyButton }
3-2 其他项目中使用组件库
packages目录下其他项目中通过 pnpm add @tbc/ui
安装依赖,依赖详情为 "workspace:^"
。
使用组件库
<template>
<MyButton>自定义组件库</MyButton>
</template>
<script setup>
import { MyButton } from "@tbc/ui"
</script>