pnpm与 npm/yarn 的区别及使用

5,352 阅读7分钟

要知道pnpm 的优势在哪里?它解决了什么问题?就要知道 pnpm 的作者在开发 pnpm 的时候,他认为的 npm/yarn 的弱点在哪里。

npm的历史

1.npm

npm没出现之前 都是通过下载一些压缩过的js包,或者通过别人部署在cdn等线上js压缩包,这样的方式不管是贡献自己的js包,还是引用别人的js包都极其麻烦。没有合适的地方寻找,不能简单的升级版本。

npm出现的好处

  • 中央仓库npm服务器收集了大量的依赖包,有较为完善的文档,并且安装简单。
  • 用户能够快速升级自己依赖包
  • 使用package.json清晰管理到项目所依赖的包及其版本号
  • 使用nodemodule形式本地安装依赖,减小了本地依赖压缩js库的大小
  • 允许用户将自己编写的包或命令行程序上传到npm服务器供别人使用。

2.yarn

yarn出现解决了npm的痛点:

  • 痛点1:npm install会进行顺序安装package.json的包,yarn采用了并行安装,大大提升了安装速度。
  • 痛点2:npm每次安装都要重新安装,yarn采用了缓存的方式,同样内容不会重复安装。
  • 痛点3: npm之前没有使用lock.json,安装版本混乱,后面npm更新汲取了yarn的lock优点

3.pnpm

3.1为什么要用pnpm?

pnpm的优点:

  • 节省磁盘空间
  • 速度快

npm和yarn本地如果有100个项目使用了同一个依赖吗,那么每个项目都会有这个依赖的副本,这样一来占用巨大的磁盘空间,二来一个文件升级过后会更改这个副本的全部内容进行重新安装。

使用pnpm,他的方式是100个项目用到一个依赖,他会将不同项目中的版本之间的差异存储在本地类似中央仓库的样子,这样一个依赖包假设有50个文件,两个同依赖但不同版本不会全部修改,只会更改如1个文件的形式,比较完美的解决npm和pnpm的两个缺点

  • 安全性

npm安装包的安全性问题,如果 A 依赖 B, B 依赖 C,那么 A 当中是可以直接使用 C 的,但问题是 A 当中并没有声明 C 这个依赖,因此会出现这种非法访问的情况。 pnpm采用的方式是依赖分割 npm安装一个指定依赖包express的node_module目录

image.png

pnpm安装的一个指定依赖的node_module目录,全是通过.pnpm做目录结构依赖树,也就是根目录不是平铺的,只显示你所需要那个依赖express(注意根目录的express只是显示,里面仍然没东西)

image.png

express所依赖的是不会被展示在根目录的,通过.pnpm里面的关系树软链到真实的代码目录

image.png

  • 支持monorepo,不用使用繁杂的lerna。

传统 npm/yarn 扁平化的安装机制

以npm@3+的安装机制为例: image.png 图片源自:npm install模块安装机制

npm install安装是在确定了包的版本后,获取包信息,构建依赖树然后进行扁平化处理,将所有依赖包安装在同一层级。

扁平化后的node_modules目录如下:

node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json

扁平化可以在使用依赖包的过程中,让所有依赖都在同一层级去找,解决了npm@3之前的版本暴露的问题:

  • 将所有依赖包安装成树结构之后,大量重复的安装了依赖,造成安装速度非常慢,磁盘空间管理非常差
  • 依赖的层级太深导致文件路径过长

但是依赖扁平化后,就会出现下列问题:

  1. 模块可以访问没有声明依赖的包
  2. 扁平化一个依赖树的算法非常复杂(耗时)
  3. 在项目的中有一些包被重复安装

问题3我没有理解,在下面这篇文章中找到了答案👇

@作者:神三元 关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?

讲解如下:

假如现在项目依赖两个包 foo 和 bar,这两个包的依赖又是这样的:

那么 npm/yarn install 的时候,通过扁平化处理之后,究竟是这样

还是这样?

答案是: 都有可能。取决于 foo 和 bar 在 package.json中的位置,如果 foo 声明在前面,那么就是前面的结构,否则是后面的结构。

这就是为什么会产生依赖结构的不确定问题,也是 lock 文件诞生的原因,无论是package-lock.json(npm 5.x才出现)还是yarn.lock,都是为了保证 install 之后都产生确定的node_modules结构。

尽管如此,npm/yarn 本身还是存在扁平化算法复杂package 非法访问的问题,影响性能和安全。

不扁平化,pnpm如何解决问题

树结构会出现问题,扁平化后也会出现问题,那么pnpm是怎么解决的呢?

pnpm在安装时,将每一个包,和它们的依赖放到同一个文件夹中。将依赖提升至和包同一层级。同时,形成与依赖树结构一致的软链接,标注每一个依赖的位置。

以express为例,当包安装完成后,我们可以看到在项目的 node_modules文件夹下,目录结构是这样的:

.pnpm
.modules.yaml
express

再打开express

▾ node_modules
    ▸ .pnpm
    ▾ express
        ▸ lib
          History.md
          index.js
          LICENSE
          package.json
          Readme.md
          .modules.yaml

这里的express只是一个符号链接,当 Node.js 解析依赖的时候,它使用这些依赖的真实位置,所以它不保留符号链接。而依赖包的真正位置,在.pnpm目录下。虽然.pnpm目录中是扁平化的结构,但是软连接的结构与依赖树完全一致。这样也解决了包的重复安装和对文件的使用权限问题。

这样的机制带来哪些优势

  • npm/yarn在不同项目依赖同一个包的情况下,会将这个包安装多次在每个项目中,而pnpm安装的包会存储在可寻址的磁盘中,在多个项目同时引用时,只需要用一个硬链接指向该地址就可以使用,大大节约了磁盘空间
  • 当依赖了同一个包的不同版本时,只对变更的文件进行更新,不需要重复下载没有变更的部分,对于时间和空间
  • 算法比 npm/yarn 的扁平化算法简单很多,节省时间

pnpm 使用

安装

在 Windows 下(使用PowerShell):

iwr https://get.pnpm.io/install.ps1 -useb | iex

然后您可以使用 pnpm env 命令来安装 Node.js。

使用 Corepack

从 v16.13 开始,Node.js 发布了 Corepack 来管理包管理器。 这是一项实验性功能,因此您需要通过运行如下脚本来启用它:

corepack enable

这将自动在您的系统上安装 pnpm。 但是,它可能不是最新版本的 pnpm。 To upgrade it, check what is the latest pnpm version and run:

corepack prepare pnpm@<version> --activate

通过 npm 安装

npm install -g pnpm

以下是各版本 pnpm 与各版本 Node.js 之间的支持表格。

Node.jspnpm 4pnpm 5pnpm 6pnpm 7
Node.js 10✔️✔️
Node.js 12✔️✔️✔️
Node.js 14✔️✔️✔️✔️
Node.js 16未知未知✔️✔️
Node.js 18未知未知✔️✔️

直接按照官网方法安装就ok 👉 pnpm.io/zh/installa…

使用

CommandMeaning
pnpm add sax保存到 dependencies
pnpm add -D sax保存到 devDependencies
pnpm add -O sax保存到 optionalDependencies
pnpm add -g saxInstall package globally
pnpm add sax@next从 next 标签下安装
pnpm add sax@3.0.0安装指定版本 3.0.0

功能

npm 命令pnpm 等效
npm installpnpm install
npm i <pkg>[pnpm add <pkg>]
npm run <cmd>[pnpm <cmd>]

基本用法与npm大差不差,但是在配置上, pnpm 比 npm 更严格。

pnpm 支持丰富选择器语法,可以通过名称或关系选择包。

可以通过 --filter 标志指定选择器:

pnpm <command> --filter <package_selector>

详细配置信息可在官方文档查询 👉 pnpm.io/zh/package_…

参考资料: