使用pnpm workspace管理Monorepo架构
在开发项目的过程中,我们需要在一个仓库中管理多个项目,每个项目有独立的依赖、脚手架,这种形式的项目结构我们称之为Monorepo
,pnpm workspace
就是管理这类项目的方案之一。
一、pnpm简介
1、pnpm概述
pnpm
代表performance npm
(高性能的npmn
),同npm
和yarn
,都属于javascript
包管理安装工具,它较npm
和yarn
在性能上得到很大提升,被称为 快速地,节省磁盘空间的包管理工具。
2、pnpm优点
-
快速:pnpm会将包缓存到本地,减少二次安装需要的时间。
-
节省磁盘空间:他会把包软链到项目本地,不需要反复安装。
-
节省网络带宽:同样的道理
-
更好的依赖处理逻辑
3、对比lerna+yarn
使用lerna+yarn
组合,也可以实现Monorepo
项目管理。但是相比来说,更推荐pnpm workspace
来管理。
原因如下:
当使用npm
和yarn
时,如果你有100个项目使用了某个依赖,就会有100份该依赖的副本保存在硬盘上,而在使用pnpm
时,依赖会被存储在内容可寻址的存储中。
-
如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么
pnpm update
时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。 -
所有文件都会存储在硬盘上的某一位置。 当软件包被安装时,包里的文件会硬链接到这一位置上对应的文件,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。
4、安装pnpm
如果已经安装了npm
,直接使用npm i -g pnpm
进行全局安装。
二、构建一个Monorepo项目
先上最终的项目结构:
Monorepo
- umi-antd
- web
-- common1
-- common2
如上图,我们最终要创建如上图的一个项目结构,其中umi-antd
是主项目,web
文件夹下的都是子项目,供umi-antd
调用。
1、创建主项目
主项目这里是使用的umi
项目,这里也可以构建基于其他打包工具的各类项目,只要是遵守package.json
配置条例的项目,都是可以的。
构建umi项目
pnpm dlx create-umi@latest
这里选择创建Ant Design Pro
项目。
$ npx create-umi@latest
? Pick Umi App Template › - Use arrow-keys. Return to submit.
Simple App
❯ Ant Design Pro
Vue Simple App
2、创建子项目common1
-
进入
Monorepo>web
目录,创建common1
文件夹 -
然后进入
common1
目录,执行命令npm init
对项目进行初始化,这时候会生产一个package.json
文件 -
新建
index.ts
文件,代码如下:export default function print(msg:string) { console.log(msg); }
3、创建子项目common2
步骤和创建common1
一致
4、主项目中引入子项目
安装common1
和common2
。
pnpm -F umi-antd add common1
pnpm -F umi-antd add common2
安装完成后,在umi-antd
依赖中出现两个子项目的软链接(或者叫符号链接)。
在业务代码中调用子项目输出的方法:
//umi-antdsrcpagesHomeindex.tsx
import Guide from '@/components/Guide';
import { trim } from '@/utils/format';
import { PageContainer } from '@ant-design/pro-components';
import { useModel } from '@umijs/max';
import styles from './index.less';
import print1 from 'common1';
import print2 from 'common2';
const HomePage: React.FC = () => {
const { name } = useModel('global');
print1("umi-antd中调用common1");
print2("umi-antd中调用common2");
return (
<PageContainer ghost>
<div className={styles.container}>
<Guide name={trim(name)} />
</div>
</PageContainer>
);
};
export default HomePage;
5、运行主项目
到这里基本搭建完毕,这时候的整体项目结构如图:
然后执行命令启动主项目:pnpm -F umi-antd start
,我们可以看到,控制台打印出来子模块中的信息。
6、子模块调用子模块
子模块也是可以相互调用的,这里我们在common1
中引入common2
的方法。
-
执行命令
pnpm -F common1 add common2
-
在
common1
中调用common2
中方法,代码如下://webcommon1index.ts import print2 from 'common2'; export default function print(msg:string) { console.log(msg); print2("这里是common1中调用common2"); }
再次运行umi-antd
项目时,我们可以看到,控制台已经打印了common2
中方法。
三、workspace协议(workspace:)
1、协议概述
默认情况下,如果可用的 packages 与已声明的可用范围相匹配,pnpm 将从工作区链接这些 packages。 例如, 如果bar
引用"foo": "^1.0.0"
并且foo@1.0.0
存在工作区,那么pnpm会从工作区将foo@1.0.0
链接到bar
。 但是,如果 bar
的依赖项中有 "foo": "2.0.0"
,而 foo@2.0.0
在工作空间中并不存在,则将从 npm registry 安装 foo@2.0.0
。 这种行为带来了一些不确定性。
幸运的是,pnpm 支持 workspace 协议 workspace:
。 当使用此协议时,pnpm 将拒绝解析除本地 workspace 包含的 package 之外的任何内容。 因此,如果您设置为 "foo": "workspace:2.0.0"
时,安装将会失败,因为 "foo@2.0.0"
不存在于此 workspace 中。
当 link-workspace-packages 选项被设置为 false
时,这个协议将特别有用。 在这种情况下,仅当使用 workspace:
协议声明依赖,pnpm 才会从此 workspace 链接所需的包。
以上是官网的解释,实际在使用的时候,如果引入的项目需要强制使用本地包,则可以使用workspace:
协议。改造下umi-antd
的package.json
文件如下:
"dependencies": {
"@ant-design/icons": "^5.0.1",
"@ant-design/pro-components": "^2.4.4",
"@umijs/max": "^4.0.76",
"antd": "^5.4.0",
"common1": "workspace:^",
"common2": "workspace:^"
},
上面加上common1
和common2
的依赖声明后,会强制到本地寻找,找不到也不会到npm registry
对应的私仓去下载。
2、版本符号
workspace协议,区分*
、~
、^
、版本号
,表示的意义各部相同,具体如下:
{
"dependencies": {
"foo": "workspace:*",
"bar": "workspace:~",
"qar": "workspace:^",
"zoo": "workspace:^1.5.0"
}
}
///将会被转化为:
{
"dependencies": {
"foo": "1.5.0",
"bar": "~1.5.0",
"qar": "^1.5.0",
"zoo": "^1.5.0"
}
}
workspace协议官方说明:官方地址