Pnpm是什么
Fast, disk space efficient package manager
pnpm特性
pnpm依赖管理
在npm1, npm2中,node_modules中的依赖呈现嵌套结构:
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
这样的设计存在如下问题:
- 依赖层级太深,会导致文件路径过长的问题,尤其在 window 系统下。
- 大量重复的包被安装,文件体积超级大。比如跟
foo
同级目录下有一个baz
,两者都依赖于同一个版本的lodash
,那么 lodash 会分别在两者的 node_modules 中被安装,也就是重复安装。
从 npm3 开始,包括 yarn,都着手来通过扁平化依赖
的方式来解决这个问题,解决了大量包重复安装的问题,而且依赖层级也不会太深。
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
单扁平化依赖带来了新的问题:扁平化算法本身的复杂性很高,耗时较长;项目中仍然可以非法访问没有声明过依赖的包。
pnpm采用自己独特的机制解决了上述问题:
举个栗子
两个代码相同的项目,一个使用npm,一个使用pnpm,依赖均只有express
,但是在代码中均使用iconv-lite
(express的依赖),npm项目成功执行,pnpm失败。
npm: pnpm:
\
npm使用扁平化的node_modules,存在安全问题,可以未经package.json声明而直接只用依赖;
pnpm使用了软链接的方式,真正的包安装在了.pnpm目录下,只会对项目中package.json中声明的依赖包,放置在node_modules目录下。
npm: pnpm:
进一步观察pnpm项目,node_modules中express是一个软链接,没有自己的node_modules,项目中真正的依赖包在.pnpm目录下面。
Jinru
进入node_modules/.pnpm/express@4.18.1/node_modules
目录进一步查看,只有express是真实目录,其他express的依赖又是软链接,真实的依赖包还是在.pnpm/name@x.x.x/node_modules/name
目录下
\
.pnpm
目录下呈现的是扁平的目录结构,但顺着软链接
慢慢展开,其实依赖是嵌套的结构。
将包本身
和依赖
放在同一个node_module
下面,与原生 Node 完全兼容,又能将 package 与相关的依赖很好地组织到一起,设计十分精妙。
上述机制能顺利运行的原因是Nodejs的node_modules目录加载机制,不断向上层目录的node_modules
目录寻找包。
▾ node_modules
▾ .pnpm
▸ accepts@1.3.5
▸ array-flatten@1.1.1
...
▾ express@4.16.3
▾ node_modules
▸ accepts
▸ array-flatten
▸ body-parser
▸ content-disposition
...
▸ etag
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
\
pnpm的依赖管理机解决了扁平化包带来的未声明依赖的安全问题。
Pnpm的高效存储
用 npm/yarn 的时候,每个项目的依赖都是单独安装,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代码。
但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,pnpm会在磁盘上新建一个中心化的pnpm store来保存所有的依赖, 后面在项目中再次使用都会直接使用 hardlink(硬链接),将项目中的包link到store,从而大大节省磁盘空间
Pnpm monorepo
pnpm需要通过 pnpm-workspace.yaml
文件定义工作空间,每个具体的包放在工作空间写入的目录下,且包名一般使用命名空间比如@mono/xxx
对于全局安装的依赖包,可使用 pnpm install typescript -D -W
,局部package中使用的依赖包有两种方式添加:
cd packges/xxx
后,执行pnpm install xxx
- 不切换到package/xxx目录,直接在根目录下添加
pnpm add xxx --filter @mono/package-a
项目内的包互相安装依赖:pnpm add @mono/utils --filter @mono/serve
最终Monorepo项目的结构如下图,全局安装的依赖在root目录的node_modules下并软链接到.pnpm目录下;
局部包的依赖在自己的node_modules目录下,其中其他局部包软链接到项目其他包,网络下载的包软链接到.pnpm目录下。