1.为什么选择 pnpm 作为包管理器?
- Workspace 支持
原生支持 Monorepo
pnpm 原生支持 Monorepo 项目,通过 pnpm-workspace.yaml
文件,你可以轻松地配置多个子项目。pnpm 的 workspace 功能使得在多个子项目之间共享依赖变得更加容易,并且可以自动处理依赖的安装和更新。
依赖共享
在 Monorepo 项目中,pnpm 的 workspace 功能允许你在多个子项目之间共享依赖。例如,你可以使用 workspace:*
来引用本地 workspace 中的包,而不需要发布到 npm 仓库。
- 性能优势
快速安装
pnpm 使用了一种称为“内容寻址存储”(Content-Addressable Storage)的技术,这意味着相同的依赖包只会被下载和存储一次。这大大减少了安装时间,尤其是在大型 Monorepo 项目中,多个子项目共享相同的依赖时,pnpm 的性能优势尤为明显。
节省磁盘空间
由于 pnpm 只存储每个依赖包的一个副本,因此它可以显著减少磁盘空间的占用。这对于 Monorepo 项目尤为重要,因为多个子项目可能会共享大量的依赖包。
- 依赖管理
严格的依赖解析
pnpm 使用了一个严格的依赖解析算法,确保每个包的依赖版本都是一致的。这减少了由于依赖版本不一致而导致的潜在问题,特别是在 Monorepo 项目中,多个子项目共享相同的依赖时。
扁平化的 node_modules 结构
pnpm 的 node_modules
结构是扁平化的,这意味着每个包都有自己的依赖目录,而不是将所有依赖放在根目录下。这种结构减少了依赖冲突的可能性,并且使得依赖关系更加清晰。
- 安全性
避免幽灵依赖
pnpm 的 node_modules
结构避免了“幽灵依赖”(Phantom Dependency)问题。幽灵依赖是指在 node_modules
中意外地引入了未在 package.json
中声明的依赖包。pnpm 通过严格的依赖解析和扁平化的 node_modules
结构,减少了这种问题的发生。
- 灵活性
支持多种包管理方式
pnpm 支持多种包管理方式,包括 npm、yarn 等。你可以轻松地将现有的项目迁移到 pnpm,而不需要对项目结构进行大的改动。
2.初始化项目
- 创建项目目录
首先新建一个文件夹,名为 vue3-pnpm-monorepo
进入 vue3-pnpm-monorepo
文件夹,初始化一个默认的 package.json
文件,执行命令:
mkdir vue3-pnpm-monorepo
cd vue3-pnpm-monorepo
pnpm init
2. 配置 pnpm workspace
为了在 Monorepo 中管理多个项目,我们需要配置 pnpm workspace。在项目根目录下创建一个 pnpm-workspace.yaml
文件,并添加以下内容:
packages:
- 'packages/*'
这表示所有位于 packages
目录下的子目录都将被视为独立的包(项目)。这个文件可以帮助我们在安装公共依赖的情况下,也将 packages
下的项目所需要的依赖也同时进行安装。
- 创建子项目目录
mkdir packages
- 初始化三个
vue3 + ts
的项目进行演示
cd packages
npm init vite vue-demo1
npm init vite vue-demo2
npm init vite vue-demo3
当前项目结构如下:
├── packages
| ├── vue-demo1
| ├── vue-demo2
| └── vue-demo3
├── package.json
项目内部结构如下:
├── packages
| ├── vue-demo1
| | ├── .vscode
| | ├── public
| | ├── src
| | ├── .gitignore
| | ├── index.html
| | ├── package.json
| | ├── README.md
| | ├── tsconfig.json
| | ├── tsconfig.node.json
| | └── vite.config.ts
| ├── vue-demo2
| └── vue-demo3
├── package.json
每个子项目的 package.json
内容应该如下:
{
"name": "vue-demo1",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.10"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"typescript": "^5.5.3",
"vite": "^5.4.8",
"vue-tsc": "^2.1.6"
}
}
经过分析不难发现,目前这三个项目是完全一样的,需要的依赖也是完全一样的,所以这些依赖项就可以直接抽离出来,变成公共的依赖项,添加上版本号,另外调试的话也不需要在这里进行调试,也直接删掉,稍加修改这个文件,最后如下:
vue-demo1:
{
"name": "vue-demo1",
"private": true,
"version": "0.0.0"
}
vue-demo2:
{
"name": "vue-demo2",
"private": true,
"version": "0.0.0"
}
vue-demo3:
{
"name": "vue-demo3",
"private": true,
"version": "0.0.0"
}
创建公共依赖配置
- 接下来就需要将三个公共的依赖项,进行配置到根目录,使用全局的依赖包提供这三个项目使用
- 在 根目录下的
package.json
新增之前抽离出来的公共配置项,都添加到公共的配置文件中 - 新增调试的命令,一般启动项目可以使用
dev:项目名
来进行分别启动项目,后面跟上需要启动的路径即可
{
"name": "vue3-pnpm-monorepo",
"version": "1.0.0",
"description": "",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"dev:vue-demo1": "vite packages/vue-demo1",
"dev:vue-demo2": "vite packages/vue-demo2",
"dev:vue-demo3": "vite packages/vue-demo3"
},
"dependencies": {
"vue": "^3.5.10"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"typescript": "^5.5.3",
"vite": "^5.4.8",
"vue-tsc": "^2.1.6"
},
"keywords": [],
"author": "HKH",
"license": "ISC"
}
配置好之后,就可以在根目录执行:
pnpm i
安装好了之后,我们就会发现,在项目的根目录中,有了 node_modules
文件夹。
接下来可以通过命令启动一下项目:
pnpm run dev:vue-demo1
pnpm run dev:vue-demo2
pnpm run dev:vue-demo3
3.局部安装依赖项
当然,我们也会遇到一种情况就是,我们的,某个子项目需要某个依赖,但是其他项目没有使用,所以这个时候最好就是单独为这个子项目安装依赖,比如说,我的 vue-demo1
的项目中需要安装 element-plus
,而其它的两个项目是不需要的,那么这样的话,就可以将 element-plus
单独安装到 vue-demo1
的项目中,而另外两个项目是不需要的,所以就没必要安装到全局,直接安装到 vue-demo1
内部,安装的方式有两种:
- 进入到指定目录去安装
可以直接进入到指定需要安装的目录进行安装,那么进入到 packages/vue-demo1
中,执行:
cd packages/vue-demo1
pnpm i element-plus
--filter
安装 使用--filter
修饰符可以实现在根目录指定某个目录进行安装,具体命令为:
pnpm i element-plus --filter vue-demo1
5.配置共享库
- packages 下面新建 shared-utils 目录,再新建 index.ts 文件,如图:
// packages/common/shared-utils/index.js
export const name:string = '张三'
export const age:number = 18
export const sayHello:Function = () => console.log('Hello World!!!')
- shared-utils 下新建 package.json ,内容如下:
{
"name": "shared-utils",
"version": "0.0.0",
"main": "index.ts",
"license": "MIT"
}
- 假设我们需要在 vue-demo1 中使用,则还需配置如下:
确保根目录的 package.json
中 workspaces
配置正确:
{
"name": "vue3-pnpm-monorepo",
"version": "1.0.0",
"description": "",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"dev:vue-demo1": "vite packages/vue-demo1",
"dev:vue-demo2": "vite packages/vue-demo2",
"dev:vue-demo3": "vite packages/vue-demo3"
},
"dependencies": {
"vue": "^3.5.10"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"typescript": "^5.5.3",
"vite": "^5.4.8",
"vue-tsc": "^2.1.6"
},
"keywords": [],
"author": "HKH",
"license": "ISC"
}
在 packages/vue-demo1
目录下,编辑 package.json
文件,添加对 shared-utils
的依赖:
{
"name": "vue-demo1",
"private": true,
"version": "0.0.0",
"dependencies": {
"element-plus": "^2.8.5",
"shared-utils": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.7.5"
}
}
-
在根目录执行
pnpm install
-
修改
vue-demo1
-
解决项目中的报错提示
- 无法找到声明文件
这个错误是由于 TypeScript 无法找到 shared-utils
模块的类型声明文件。我们可以通过这种方式解决这个问题: 使用 tsconfig.json
配置, vue-demo1
项目的 tsconfig.json
中添加 typeRoots
配置:
{
"compilerOptions": {
"typeRoots": [
"./node_modules/@types",
"./src/types"
],
// "types": ["node"],
},
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
在 src/types
目录下创建 shared-utils.d.ts
文件:
declare module 'shared-utils' {
export const name: string;
export const age: number;
export const sayHello: () => void;
}
- shared-utils 放在 common 文件夹下面时 pnpm install 执行报错: In packages\vue-demo1: "shared-utils@workspace:*" is in the dependencies but no package named "shared-utils" is present in the workspace
这个时候需要在根目录的 pnpm-workspace.yaml 配置文件下增加目录,也就是 common 文件夹的目录,因为我们原本只写了 - 'packages/*',它的 workspace 只识别了一层,所以还需要配置 common 目录,让其识别 common 下面的目录文件,最终配置如下如下:
packages:
- 'packages/*'
- 'packages/common/*'
最后再次在根目录执行 pnpm install
即可
- 根目录执行
pnpm run dev:vue-demo1
,运行子项目,这个时候就可以发现这个子项目下可以正常使用公共工具当中导出的变量和方法啦
总结
Monorepo 的优势在于它能够在一个代码库中管理多个相关项目,从而提高代码复用性和开发效率。以下是 Monorepo 的一些主要优势:
- 代码复用
- 在 Monorepo 中,你可以轻松地在多个项目之间共享代码。通过创建共享库,你可以在不同的子项目中复用相同的代码,从而减少重复工作并提高代码的一致性。
- 统一依赖管理
- 使用 pnpm 的 workspace 功能,你可以在一个地方管理所有子项目的依赖。这不仅简化了依赖管理,还减少了重复安装相同依赖的情况,节省了磁盘空间。
- 简化开发流程
- 在 Monorepo 中,你可以使用统一的工具链和配置文件来管理多个项目。这简化了开发流程,使得开发者可以更专注于业务逻辑的实现,而不是配置和工具的调整。
- 更好的版本控制
- 在 Monorepo 中,所有项目都在同一个代码库中,这使得版本控制更加容易。你可以轻松地跟踪代码的变化,并在需要时回滚到之前的版本。
- 提高团队协作
- Monorepo 使得团队成员可以更容易地共享代码和知识。通过在一个代码库中工作,团队成员可以更方便地协作,减少沟通成本,提高开发效率。
* 参考项目https://github.com/huangkaihao666/vue3-pnpm-monorepo-.git