如何使用pnpm + monorepo实现高效的前端多项目管理
常见的前端开发多项目管理方式有两种,分别是multirepo
和monorepo
。今天,咱们就深入探讨一下这两种方式,重点讲讲如何结合pnpm
来实现更高效的monorepo
项目管理。
一、multirepo与monorepo介绍
(一)multirepo管理模式
multirepo
这种管理方式,简单来说,就是把每个项目或者模块都单独放在各自的git
仓库里。这就好比每个项目都是一个独立的“小房子”,各自住在不同的“地方”,需要我们分别去维护这些“小房子”。它的项目架构类似下面这样:
app/ # 项目A (git@github.com/app.git) app2/ # 项目B (git@github.com/app2.git) share/ # 公共模块 (git@github.com/share.git)
这种方式的好处是各个项目之间相互独立,互不干扰。但缺点也很明显,每个仓库都要单独维护,管理成本比较高,尤其是当公共模块有更新时,在多个项目中同步更新就会变得很麻烦。
(二)monorepo管理模式
monorepo
则是另一种思路,它把多个项目或者模块都放在同一个仓库里。在这个“大仓库”里,不仅有公共的package.json
,每个项目还都有属于自己的package.json
。这就好比大家住在同一个“大院”里,每个人有自己的“小房间”,公共的东西放在“大院”的公共区域,个人的物品放在自己的“小房间”。
在依赖管理方面,对应公共的依赖可以安装到公共的node_modules
里,而每个项目特有的依赖就安装到各自的node_modules
里,这样就能实现依赖的共享。而且,各个子模块之间还能互相引用,不用非得发布成npm
包,管理起来更加高效。monorepo
的项目架构一般是这样的:
- monorepo/ - packages/ - app/ # 项目A - app2/ # 项目B - share/ # 公共模块 - package.json
二、pnpm + monorepo实战操作
接下来,咱们就动手实践一下,看看如何用pnpm
和monorepo
进行多项目管理。
(一)安装pnpm
首先,得在全局安装pnpm
。在命令行里输入下面这条命令就能完成安装:
npm install -g pnpm
这条命令会借助npm
工具,在全局环境下安装pnpm
,安装好之后,我们就能在任何地方使用pnpm
命令了。
(二)初始化monorepo项目
- 创建项目文件夹并初始化
package.json
:先创建一个名为pnpm-monorepo
的文件夹,这就是我们项目的“大本营”。然后进入这个文件夹,用pnpm init
命令初始化一个package.json
文件,它会记录项目的基本信息。
mkdir pnpm-monorepo cd pnpm-monorepo pnpm init
初始化后的package.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" }
- 创建子模块:接着,在
pnpm-monorepo
文件夹下创建一个packages
文件夹,用来存放各个子模块。这里我们创建两个子模块,分别是app
和share
。- 对于
app
模块,我们用pnpm create vite app
命令创建一个vue
项目。这个命令会基于vite
工具快速搭建一个vue
项目的基础结构。
- 对于
cd packages pnpm create vite app
创建完成后,app
模块的package.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`文件作为入口文件。在这个文件里,我们暴露一个公共方法,方便其他模块调用。比如:
// packages/share/index.js function add(a, b) { return a + b; } export default { add };
share
模块的package.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
文件,用来配置子模块。在这个文件里,我们指定packages
目录下所有模块都是monorepo
的子模块,内容如下:
# pnpm-workspace.yaml packages: - 'packages/*' # 指定 packages 目录下所有模块都为 monorepo 的子模块
到这里,一个基本的monorepo
项目架构就搭建好了,目录结构如下:
- pnpm-monorepo/ - packages/ - app/ # vue 项目 - share/ # 公共模块 - pnpm-workspace.yaml - package.json
(三)依赖管理
- 安装公共依赖:公共依赖就是每个子模块都会用到的依赖。比如,我们的
vue
项目和share
模块都依赖lodash-es
模块,就可以把它作为公共依赖安装到根目录的node_modules
里。通过添加-w
参数(-w
是--workspace-root
的简写),就能实现这一操作:
pnpm i lodash-es -w
- 安装子模块依赖:子模块自己特有的依赖,要安装到各自的
node_modules
里。例如,只有vue
项目用到了axios
模块,那就可以把它安装到app
项目的node_modules
里。通过--filter <package-name>
参数(--filter
可以简写为-F
,这里的package-name
是package.json
文件中的name
字段),可以指定安装到哪个子模块:
pnpm i axios --filter app
- 模块共享:如果想在
vue
项目(app
模块)里引用share
模块的add
方法,就需要把share
模块安装到app
项目的node_modules
里。可以通过--workspace
参数,让pnpm
去当前workspace
查找相关模块,命令如下:
pnpm i share --filter app --workspace
执行完这个命令后,app
项目的packages.json
文件里就会多一个share
模块的依赖,类似这样:
... "dependencies": { "share": "workspace:^", "vue": "^3.5.13" } ...
这里的workspace
表示当前工作空间,^
表示安装最新版本。当然,我们也可以直接在package.json
的依赖里声明"share": "workspace:^"
,这样在安装时就不用再加--workspace
参数了。如果想把share
模块作为公共依赖安装,在后面再加一个-w
参数就行:
pnpm i share --workspace -w
三、总结
最后,咱们来总结一下常用的pnpm
命令:
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
里,作为公共依赖。
通过pnpm
和monorepo
的结合使用,我们能够更高效地管理多项目,减少依赖管理的复杂性,提高开发效率。希望大家在实际项目中可以尝试运用这种方式。