前言
都2202了,不会还有人没听过pnpm吧(手动狗头🐶)。如果真没听过,看看这篇文章定会让你有所收获。
首先放张pnpm的截图,从截图中我们可以看到pnpm的周下载量已经达到90w+了,足以证明他的强大了。接下来我们将通过what、why、how三个方面展开介绍。
什么是pnpm(what)
pnpm 的官方文档是这样说的:
快速的,节省磁盘空间的包管理工具
因此,pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的两个优势在于:
- 节约磁盘空间并提升安装速度
- 创建非扁平化的 node_modules 文件夹
节约磁盘空间并提升安装速度
当使用 npm 或 Yarn 时,如果你有 100 个项目使用了某个依赖(dependency),就会有 100 份该依赖的副本保存在硬盘上。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:
- 如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么
pnpm update
时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。 - 所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。
因此,您在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!
创建非扁平化的 node_modules 文件夹
使用 npm 或 Yarn Classic 安装依赖项时,所有包都被提升到模块目录的根目录。 因此,项目可以访问到未被添加进当前项目的依赖。
默认情况下,pnpm 使用软链的方式将项目的直接依赖添加进模块文件夹的根目录。 如果你想了解 pnpm 关于 node_modules
结构设计的更多细节,以及为什么它在 Node.js 生态成为了后起之秀,请参考:
它的安装也非常简单。(不过你得保证你的node主版本大于10)
npm i -g pnpm
为什么要用pnpm(why)
1.安装速度更快
这里附上一个非常直观的安装速度对比(详细对比了npm、pnpm、yarn、yarn pnp在有无缓存,lock文件,node_modules文件情况下的详细时间)。该应用程序在package.json
这里
action | cache | lockfile | node_modules | npm | pnpm | Yarn | Yarn PnP |
---|---|---|---|---|---|---|---|
install | 48.4s | 18s | 21.7s | 28s | |||
install | ✔ | ✔ | ✔ | 2s | 1.4s | 2.8s | n/a |
install | ✔ | ✔ | 10.4s | 4.7s | 8.6s | 1.8s | |
install | ✔ | 15.6s | 8.1s | 14.5s | 7.6s | ||
install | ✔ | 27.8s | 16.4s | 14.9s | 21s | ||
install | ✔ | ✔ | 2.5s | 2.8s | 8.8s | n/a | |
install | ✔ | ✔ | 2s | 1.4s | 9.3s | n/a | |
install | ✔ | 2.5s | 9.5s | 15.1s | n/a | ||
update | n/a | n/a | n/a | 2s | 11.9s | 20.4s | 34.9s |
可以看到,作为黄色部分的 pnpm,在绝多大数场景下,包安装的速度都是明显优于 npm/yarn,速度会比 npm/yarn 快 2-3 倍。
安装快速原因
看到了效果之后,让我们深入了解一下为啥与其他“传统”包管理器相比,为什么 pnpm 如此疯狂?
pnpm 没有安装的阻塞
阶段。每个依赖都有自己的阶段,下一个阶段会尽快开始。
首先看传统的包管理器(npm、yarn)安装过程,他们会统一去执行解析,解析完成后执行请求,统一请求完成后再一并写入到node_modules,这当中如果某个过程很慢,会导致后续过程一直处于等待中。即安装时间 = resolve时间(all package) + fetch时间(all package) + white时间(all package)
执行npm i 或者 yarn 命令后,首先会构建依赖树,然后针对每个节点下的包,会经历下面三个步骤:
-
- 将依赖包的版本区间解析为具体的版本号
-
- 下载对应版本依赖的 tar 包到本地离线镜像
-
- 将依赖从离线镜像解压到本地缓存,将依赖从缓存拷贝到当前目录的 node_modules 目录
而pnpm则是为每个依赖去独立执行安装
过程,安装时间 = resolve时间(最耗时包) + fetch时间(最耗时包) + white时间(最耗时包)
2.更高效的利用磁盘空间
pnpm 内部使用基于内容寻址
的文件系统来存储磁盘上所有的文件,这个文件系统出色的地方在于:
- 不会重复安装同一个包。用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代码。但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用
hardlink
(硬链接,不清楚的同学详见这篇文章)。 - 即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的
hardlink
,仅仅写入那一个新增的文件
。
3.可管理node环境
安装并使用指定版本的 Node.js
安装 LTS 版本的 Node.js:
pnpm env use --global lts
pnpm env use --global argon
安装 v16 的Node.js
pnpm env use --global 16
安装 Node.js 的预发行版本
pnpm env use --global nightly
pnpm env use --global rc
pnpm env use --global 16.0.0-rc.0
pnpm env use --global rc/14
安装最新版本的 Node.js:
pnpm env use --global latest
配置项
--global, -g
此更改将全局生效。
4. 安全性高
之前在使用 npm/yarn 的时候,由于 node_module 的扁平结构,如果 A 依赖 B, B 依赖 C,那么 A 当中是可以直接使用 C 的,但问题是 A 当中并没有声明 C 这个依赖。因此会出现这种非法访问的情况。但 pnpm 脑洞特别大,自创了一套依赖管理方式,很好地解决了这个问题,保证了安全性。
如何使用pnpm(how)
以下是简便的 npm 命令等效列表,可帮助您入门:
npm 命令 | pnpm 等效 |
---|---|
npm install | pnpm install |
npm i <pkg> | [pnpm add <pkg> ] |
npm run <cmd> | [pnpm <cmd> ] |
当你使用一个未知命令时, pnpm 会查找一个具有指定名称的脚本, 所以 pnpm run lint
和 pnpm lint
相同. 如果没有指定名称的脚本,那么pnpm将以shell脚本的形式执行该命令,所以你可以做类似pnpm eslint
的事情(查阅 pnpm exec).
拓展
配置package.json中依赖版本的^和~区别
1.标准版本
SemVer
规范的标准版本号采用X.Y.Z
的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须以数值来递增。
- 主版本号(
major
):当你做了不兼容的API 修改 - 次版本号(
minor
):当你做了向下兼容的功能性新增 - 修订号(
patch
):当你做了向下兼容的问题修正。
2.先行版本
当某个版本改动比较大、并非稳定而且可能无法满足预期的兼容性需求时,你可能要先发布一个先行版本。我们可以给这个不稳定的版本贴上标签beta,因为一般 npm install 的版本都是@latest
- 内部版本(
alpha
):如1.0.0-alpha.1
- 公测版本(
beta
):如1.0.0-beta.1
- 正式版本的候选版本
rc
: 即Release candiate,如
1.0.0-rc.1
3.依赖版本管理
我们经常看到,在 package.json 中各种依赖的不同写法:
"dependencies": {
"signale": "1.4.0",
"figlet": "*",
"react": "16.x",
"table": "~5.4.6",
"yargs": "^14.0.0"
}
前面三个很容易理解:
"signale": "1.4.0",// 固定版本号
"figlet": "*",//任意版本(>=0.0.0)
"react": "16.x",//匹配主要版本(>=16.0.0 <17.0.0)
"react": "16.3.x",//匹配主要版本和次要版本(>=16.3.0 <16.4.0)
再来看看后面两个,版本号中引用了 ~ 和 ^ 符号:
~
: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 z 的最新的版本。即保持主版本号、次版本号不变的情况下,保持修订号
的最新版本。
^
: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 y 和 z 都为最新版本。即保持主版本号不变的情况下,保持次版本号、修订版本号
为最新版本。
参考:
npm版本控制遵循SemVer规范
.lock文件
npm在npm3版本中曾推出shrinkwrap,用来解决项目依赖版本号的问题,不过并没有取的理想的效果,但是通过后面npm4和npm5不断迭代,后来产生了package.lock(pm v5 中只需要 package-lock.json 就可以保正确的 node_modules 目录结构)。
使用yarn作为包管理工具的项目会生成yarn.lock(yarn 需要同时拥有 yarn.lock 文件和 package.json文件)。
使用pnpm则为生成pnpm-lock.yaml
他们存在的目的都是一样的,都是为了解决 ‘在我的电脑上明明是好的啊,怎么到你那里就不行了’ 的问题。
总结
如果你正苦于安装依赖等待时间太久,并且烦难于在多个项目间每次都需要重复安装依赖。那么pnpm会非常适合你。现在pnpm经过几年的迭代已经到pnpm7版本了,可以放心使用噢。