pnpm + monorepo: 高效的项目管理方式
前言
在进行多项目管理时,一般有两种方式,multirepo 和 monorepo 。
multirepo
multirepo 是将每个项目或者模块分别放在各自的 git 仓库里,需要对每个仓库单独维护。
项目架构如下:
zsh
app/ # 项目A (git@github.com/app.git)
app2/ # 项目B (git@github.com/app2.git)
share/ # 公共模块 (git@github.com/share.git)
monorepo
monorepo 是将多个项目或者模块放在同一个仓库里,除了有公共的 packages.json 之外,每个项目都有自己的 packages.json。
对应公共的依赖可以安装到公共的 node_modules 里,而每个项目的依赖则安装到各自的 node_modules 里,实现依赖共享
同时,每个子模块之间可以实现互相引用,而不需要发布成 npm 包,管理更加高效。
项目架构如下:
zsh
- monorepo/
- packages/
- app/ # 项目A
- app2/ # 项目B
- share/ # 公共模块
- package.json
pnpm + monorepo
接下来讲一下如何使用 pnpm + monorepo 来进行多项目管理。
安装 pnpm
首先,全局安装 pnpm。
zsh
npm install -g pnpm
初始化 monorepo
创建一个 pnpm-monorepo 文件夹,然后通过 pnpm init 创建 package.json。
zsh
mkdir pnpm-monorepo
cd pnpm-monorepo
pnpm init
json
// package.json
{
"name": "pnpm-monorepo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
然后,创建一个 packages 文件夹,用来存放子模块。
我们在 packages 文件夹下创建两个子模块:app 和 share。
对于app 模块,我们用 pnpm create vite app 创建一个 vue 项目。
zsh
cd packages
pnpm create vite app
json
// packages/app/package.json
{
"name": "app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.13"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.2",
"vite": "^6.3.1"
}
}
而对于share 模块,我们用 pnpm init 初始化一个项目,然后新建一个 index.js 文件。作为入口文件,在里面暴露一个公共方法:
javascript
// packages/share/index.js
function add(a, b) {
return a + b;
}
export default { add };
json
// packages/share/package.json
{
"name": "share",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
配置 workspace
在 pnpm-monorepo 文件夹下,创建 pnpm-workspace.yaml 文件,配置子模块。
yaml
# pnpm-workspace.yaml
packages:
- 'packages/*' # 指定 packages 目录下所有模块都为 monorepo 的子模块
这个时候一个基本的 monorepo 项目架构就创建好了:
zsh
- pnpm-monorepo/
- packages/
- app/ # vue 项目
- share/ # 公共模块
- pnpm-workspace.yaml
- package.json
依赖管理
如果在根目录下 执行 pnpm install,会自动安装根目录和所有子包的依赖项。
如果我们想分别安装依赖,可以通过指定参数来控制。
安装公共依赖
公共依赖是每个子模块都会用到的依赖,我们可以安装到公共的 node_modules 里。
比如,我们的 vue 项目和 share 模块都依赖了 lodash-es 模块,那么就可以作为公共依赖安装。
通过添加 -w 参数,可以安装到根目录的node_modules 里。
-w 是 --workspace-root 的简写。
zsh
pnpm i lodash-es -w
安装子模块依赖
子模块自己用到的依赖可以安装到各自的 node_modules 里。
比如,只有 vue 项目里用到了 axios 模块,那么就可以安装到 app 项目的 node_modules 里。
通过 --filter <package-name> 参数,可以指定安装到哪个子模块的 node_modules 里。
--filter 可以简写为 -F 。
注意,这里的 package-name 是 package.json文件中的 name 字段。
zsh
pnpm i axios --filter app
模块共享
如果我想在 vue 项目里引用 share 模块的 add 方法,需要将 share 模块安装到 app 项目的 node_modules 里。
可以通过 --workspace 参数,告诉 pnpm 去当前 workspace 查找相关模块。
zsh
pnpm i share --filter app --workspace
这时 app 项目的 packages.json 文件就会多一个 share 模块的依赖。
json
...
"dependencies": {
"share": "workspace:^",
"vue": "^3.5.13"
}
...
其中,workspace 表示当前工作空间,^ 表示安装最新版本。
也可以通过在 package.json 的依赖里声明 "share": "workspace:^",也可以指定具体的版本号。这样在安装时就不需要添加 --workspace 参数了。
如果想把 share 模块作为公共依赖安装的话,在后面再加一个 -w 参数就可以了。
zsh
pnpm i share --workspace -w
总结
pnpm i:安装所有的依赖,包括公共依赖和子模块依赖。pnpm i -w:安装公共依赖到根目录的node_modules里。pnpm i --filter <package-name>:安装指定子模块的依赖到node_modules里。pnpm i <package-name1> --filter <package-name2> --workspace:安装当前workspace中的模块到指定项目的node_modules里。pnpm i --workspace -w:安装当前workspace中的模块到根目录的node_modules里。