likes
comments
collection
share

pnpm 简介

作者站长头像
站长
· 阅读数 10

pnpm 简介

引入

前端圈子主要使用三种包管理器npmyarnpnpm。目前pnpm由于其较好的综合性能,正被越来越多的人所使用。就在几个月前(2023-3-22),它的npm周下载量正式超过了yarn,并且大有赶超npm的趋势(npm是真不作为,活该)。可以看出,pnpm是个很火的技术。正因如此,有必要学习一下pnpm,并深入了解它解决了哪些问题、存在哪些优势。

pnpm 简介

1. pnpm 解决了哪些问题?

pnpm出现之前,传统包管理器对于node_modules结构的设计是存在缺陷的。

  • npm 2.x使用的嵌套结构会导致相同的依赖被重复安装,这既增加了npm i命令所花的时间,也占用了大量的磁盘空间
  • 为了解决嵌套结构的问题,yarn推出了扁平结构,但该结构只能部分解决嵌套结构带来的问题。对于不同版本的相同依赖,只会有一个版本的依赖被提升,其他版本的依赖仍然会被重复安装。此外由于依赖提升,项目可以访问到自己的依赖的依赖,那么项目可以不在package.json中声明某个依赖,而是“蹭”依赖的依赖,倘若依赖更新或不再依赖原本的依赖,导致项目没办法“蹭”到依赖,这时就会报错。这个问题称为“幻影依赖”。

重复安装和幻影依赖问题都可以归咎于node_modules结构设计的不合理,而pnpm完美解决了这两个问题。它通过把依赖安装在全局,解决了依赖重复下载和解压的问题;此外,通过基于符号链接的node_modules结构,解决了幻影依赖问题。

2. pnpm 底层的原理

依赖安装在全局

使用pnpm安装依赖,所有依赖都会被安装在全局,也就是项目之外的一个公共的地方,称之为store。通过store可以存储所有下载过的依赖,对于新项目安装依赖时,如果该依赖存储于store当中,就可以直接拿来用,而无需再次下载和解压。

基于符号链接的 node_modules 结构

这部分更详细的介绍建议看官网

简单来说,pnpmnode_modules分为了.pnpm目录和项目依赖两部分。

对于.pnpm,首先会存放项目中所有的依赖(即便是间接依赖)的硬链接,并通过符号链接表明依赖之间的依赖关系。

例如项目安装了依赖于 bar@1.0.0foo@1.0.0,那么pnpm首先会创建硬链接:

node_modules
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar -> <store>/bar
    │           ├── index.js
    │           └── package.json
    └── foo@1.0.0
        └── node_modules
            └── foo -> <store>/foo
                ├── index.js
                └── package.json

bar@1.0.0foo@1.0.0是项目中用到的依赖,它们都硬链接到了store目录下的依赖。由于是使用硬链接的方式进行文件共享,因此这一步并不会占用额外的磁盘空间。如果这里不理解,可以去复习一下硬链接 & 符号链接的知识,如果忘了的话这里确实不容易理解,因为磁盘上存储的是文件的具体内容,linux实际上是通过inode得到该文件的具体内容,而硬链接会存储inode,因此并不会额外占用空间。

接下来,pnpm会使用符号链接处理依赖的依赖项:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar -> <store>/bar
    └── foo@1.0.0
        └── node_modules
            ├── foo -> <store>/foo
            └── bar -> ../../bar@1.0.0/node_modules/bar

可以看到foo@1.0.0node_modules下面多了bar的符号链接。当foo@1.0.0需要访问bar这个依赖时,Node会直接忽略符号链接,因此它通过查找node_modules,会解析到bar的实际位置.pnpm/bar@1.0.0/node_modules/bar

说完了.pnpm,再说项目的直接依赖,项目的直接依赖会通过符号依赖进行链接,它们被直接放在node_modules下:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar -> <store>/bar
    └── foo@1.0.0
        └── node_modules
            ├── foo -> <store>/foo
            └── bar -> ../../bar@1.0.0/node_modules/bar

通过这种方式,项目可以访问到foo依赖,但是却不能访问到bar依赖(因为node_modules下没有bar这个符号链接,Node当然找不到),所以就解决了幻影依赖的问题。

3. pnpm 的优势(相比于 npm)

pnpmyarn各有优劣,没办法讲哪个更好(可能pnpm略好一点),但是和npm相比,优势还是很大的:

  • 解决了幻影依赖问题:目前pnpmyarn都解决了这个问题,只有npm还没解决
  • 安装快速:由于pnpm使用了全局store存储依赖,因此对于重复的依赖,只需使用硬链接进行链接即可;而npm需要重复下载和解压,花费更多时间
  • 节省磁盘空间:pnpm对于同一依赖全局只存一个;而npm无论是嵌套结构还是扁平结构,都可能会出现同一个项目的同一个依赖安装在多处的问题
  • 支持更多的功能,例如可以添加补丁:目前pnpmyarn都支持patch命令,使用该命令可以对现有的依赖进行修改(打补丁,主要是为了解决一些依赖中还没修复的bug或是添加功能)。而npm需要使用插件,原生不支持

4. 其他资料

这里作者回答了一些问题,例如为什么不全部用符号链接,Windows 如何做的兼容

三种包管理器的对比: JavaScript Package Managers: NPM Vs YARN Vs PNPM

npm下载量对比