PNPM的使用
PNPM的使用
joesonshaw一 、NPMP包管理工具
pnpm 介绍
pnpm
指performant npm
(高性能的 npm),如 pnpm 官网所言,它是快速的,节省磁盘空间的包管理工具,同时,它也较好地支持了workspace
和monorepos
。
举例来说,如果项目中,你使用了某个依赖项的多个版本,那么
pnpm
只会将有差异的文件添加到仓库。如果某个依赖包有 100 个文件,而它的新版本只改变了其中 1 个文件。那么pnpm update
时只会添加 1 个新文件,而不会复制整个新版本的所有包。此外。所有文件都会存储在硬盘上的某一位置。 当依赖包被被安装时,其中的文件会硬链接到这一位置,而不会占用额外的磁盘空间。同时,项目中允许共享同一版本的依赖。接下来我们先了解下pnpm
的使用效果
安装配置
使用 PowerShell安装:
iwr https://get.pnpm.io/install.ps1 -useb | iex
使用npm安装(Node.js版本需v14及以上)
npm install -g pnpm
设置仓库地址
pnpm config set store-dir path-to/your-dir
以下是各版本 pnpm 与各版本 Node.js 之间的支持表格。
Node.js | pnpm 4 | pnpm 5 | pnpm 6 | pnpm 7 |
---|---|---|---|---|
Node.js 10 | ✔️ | ✔️ | ❌ | ❌ |
Node.js 12 | ✔️ | ✔️ | ✔️ | ❌ |
Node.js 14 | ✔️ | ✔️ | ✔️ | ✔️ |
Node.js 16 | 未知 | 未知 | ✔️ | ✔️ |
Node.js 18 | 未知 | 未知 | ✔️ | ✔️ |
pnpm 效果
与
npm
、yarn
、yarn pnp
工具链效果对比,来自 pnpm benchmarks
action | cache | lockfile | node_modules | npm | pnpm | Yarn | Yarn PnP |
---|---|---|---|---|---|---|---|
install | 1m 9.5s | 15.3s | 16.6s | 23.6s | |||
install | ✔ | ✔ | ✔ | 2.4s | 1.3s | 2.3s | n/a |
install | ✔ | ✔ | 14.8s | 4s | 6.8s | 1.5s | |
install | ✔ | 21.8s | 8.9s | 11.2s | 6.2s | ||
install | ✔ | 35.4s | 13.4s | 12s | 17.9s | ||
install | ✔ | ✔ | 3.1s | 1.9s | 7s | n/a | |
install | ✔ | ✔ | 2.4s | 1.3s | 7.6s | n/a | |
install | ✔ | 3s | 6.1s | 11.8s | n/a | ||
update | n/a | n/a | n/a | 2.3s | 11.8s | 15.5s | 28.3s |
从上表数据我们可以看出,pnpm
的各项性能均比其它包管理工具有优势,为什么 pnpm
有如此优越的表现?
pnpm
的原理
pnpm
主要有两个不同与其包管理工具的特性:
基于硬链接的 node_modules
pnpm
创建从全局存储到项目中 node_modules
文件夹的硬链接,而硬链接指向磁盘上原始文件所在的同一位置,具体来说就是 node_modules
中每个包的每个文件都是来自内容可寻址存储的硬链接,简言之,就是特定版本和名称的包全局只有一份。举例来看:
node_modules
└── .pnpm
├── [email protected]
│ └── node_modules
│ └── bar -> <store>/bar
│ ├── index.js
│ └── package.json
└── [email protected]
└── node_modules
└── foo -> <store>/foo
├── index.js
└── package.json
node_modules
下面的唯一文件夹叫做 .pnpm
, .pnpm
下面是一个 <PACKAGE_NAME@VERSION>
文件夹,而在其下面 <PACKAGE_NAME>
的文件夹是一个基于内容可寻址存储的硬链接。同时,我们也可以通过 pnpm root
命令来打印当前项目中存放模块(modules)的有效目录
基于依赖解析的软链接 symlinks
观察以下依赖包结构
node_modules
├── foo -> ./.pnpm/[email protected]/node_modules/foo
└── .pnpm
├── [email protected]
│ └── node_modules
│ └── bar -> <store>/bar
└── [email protected]
└── node_modules
├── foo -> <store>/foo
└── bar -> ../../[email protected]/node_modules/bar
我们可以看到在 [email protected]/node_modules/bar
内引用了 bar
的软链接 ../../[email protected]/node_modules/bar
,而在项目里引用 foo
的软链接 ./.pnpm/[email protected]/node_modules/foo
,如果项目内新增一个依赖包 [email protected]
,则其引用结构如下:
node_modules
├── foo -> ./.pnpm/[email protected]/node_modules/foo
└── .pnpm
├── [email protected]
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../[email protected]/node_modules/qar
├── [email protected]
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../[email protected]/node_modules/bar
│ └── qar -> ../../[email protected]/node_modules/qar
└── [email protected]
└── node_modules
└── qar -> <store>/qar
根据前文我们介绍到的Nodejs
的依赖解析规则,[email protected]/node_modules/foo/index.js
中所需的依赖包 bar
,实际上使用的是[email protected]/node_modules/bar
中的内容,因此,只有真正在依赖项中的包才能被访问到。而对于不同的 peer dependencies
的依赖解析原理,可以参考这里 How peers are resolved
通过**基于硬链接的node_modules
**和**基于依赖解析的软链接**原理,我们了解到,当我们在相同操作系统下第二次安装同一个依赖包时,我们需要做的仅仅是创建一个该依赖包对应的硬链接,对于同一个依赖包的不同版本,也只有不同的部分会被重新保存起来,而具体有没有 pnpm
是在哪里判断的呢?全局的 pnpm
索引文件在 ~/.pnpm-store/v3/files
。基于此,使用硬链接让依赖包的安装速度非常快,同时也去除了冗余,节省较大磁盘空间。
实际应用
- 执行
pnpm i
,- 执行
pnpm run uno
package.json
文件修改后取消安装,- 执行
pnpm i
命令后等待依赖导入完成。- 执行
npm run start
此时你会发现...
项目报错找不到依赖文件查找资料发现,
yarn
和npm
会将所有依赖平铺在node_modules
,在项目中可以访问任何依赖的包。默认配置的pnpm
创建node_modules
时不会平铺依赖,我们需要将依赖提升至node_modules
根目录。
在安装依赖之前我们需要先配置项目
- 在项目根目录创建
.npmrc
文件,添加如下配置,此配置会将所有内容提升到node_modules
的根目录。- 执行
pnpm i
,- 执行
pnpm run uno
,在package.json
文件修改后取消安装,- 执行
pnpm i
命令后等待依赖导入完成。- 执行
npm run start
auto-install-peers=true # 是否自动下载peer依赖项
shamefully-hoist=true # 是否将所有依赖包平铺在node_modules
peers依赖项文档
shamefully-hoist配置文档
如果明确知道需要提升的包名,可以按如下设置:
public-hoist-pattern[]=moment
public-hoist-pattern[]=@umijs/*
public-hoist-pattern[]=antd
public-hoist-pattern[]=umi-request
...
缺陷
Pnpm
不能跨多个驱动器或文件系统工作
包存储应与安装的位置处于同一驱动器和文件系统上,否则,包将被复制,而不是被链接。 这是由于硬链接的工作方式带来的一个限制,因为一个文件系统上的文件无法寻址另一个文件系统中的位置。 有关更多详细信息,请参阅 issue #712。