npm & yarn & pnpm 的区别

131 阅读3分钟

npm & yarn & pnpm 的区别

npm 作为 node 钦点的包管理器在平时前端的开发中被广泛使用。那又是什么原因导致了 yarn 以及 pnpm 的出现呢? 本篇文章将从 npm 功能的更迭出发分析 yarn 以及 pnpm 出现的原因以及解决了什么问题。

在 npm 的远古版本存在两个巨大的依赖管理问题,分别是:

  1. 缺少版本 lock 文件
  2. 重复依赖

版本 lock 文件

这里的版本 lock 文件指的是 npm 中的 packages.lock 或者 yarn 中的 yarn.lock 文件。在指定依赖时,由于大部分的时间开发者都不会直接指定某个确定的版本,而是指定一个范围,例如: '>=1.0.0' 那么在安装时,npm 会安装一个满足该需求的最新版本。例如在你初始化项目时 npm 下载了 1.0.0,但时某一个天,在其他同时的电脑上也安装了这个项目,此时 npm 下载了不兼容了 1.1.0。那么就会出现在你的电脑上能跑起来,但是在其他人电脑上跑不起来的问题。虽然可以不适用范围的方式指定版本,但是你并不能保证你的依赖中有没有使用范围版本管理的情况。因此后续 yarn 久使用了 yarn.lock 来保存安装时的确切版本,如果存在 yarn.lock 文件,则安装其描述的版本来安装,则不存在相同的项目依赖版本的问题了。当然现在的 npm 也支持这个功能,生成的版本信息在 packages.lock 中。

重复依赖

在 npm3 之前,npm 当多个库依赖于同一个文件时会将这些依赖安装在各自的 node_modules 之下。例如 a, b, c 三个 module 依赖于 d@1.0.0 这个 module 时,d@1.0.0 会被保存三份,例如:

image.png

这中处理方式会导致很多基础的依赖库(例如 lodash)被重复保存,造成 node_modules 占用巨大的空间。yarn 为了解决这种问题采用的拍平的方式,例如将 d@1.0.0 直接按照到根 nodes_modules 中。这样依据 module 的查找规则,先去 a,b,c 的 node_modules 中找,如果找不到则继续往上直到找到根 nodes_modules 中的 d@1.0.0。这样 d@1.0.0 就只用被保存一次。但是这样做同样还有几个问题:

  1. 生成包结构不稳定。例如 a 依赖于 d@1.0.0 但 b 只能依赖于 d2.0.0 那么到底应该将哪个版本放在根 node_modules 中呢?
  2. 由于拍平的方式会将复用的 module 直接安装到根 node_modules,意味着没有声明依赖的 module 也可以直接使用这个 module。例如我们项目依赖于 a 但 a 又依赖于 lodash,那我们就可以不声明依赖直接使用 lodash。如果某一次对 a 升级的过程中,a 去掉了 lodash 的依赖,那么我们的项目就会因为缺少 lodash 的依赖而构建失败。这也就是常常提到的幻影依赖问题。这个问题无论是 npm 还是 yarn 都存在。

pnpm

pnpm 是继 npm yarn 又一款 node package 管理工具。它有如下优点:

  1. node_modules 并不是每一个项目都是独立的软件,而是对本地依赖的一个链接。也就是多个项目公用相同的依赖,再一次节约了磁盘空间。
  2. pnpm 不会对依赖进行拍平,因此不会存在幻影依赖的问题。

那么 pnpm 是如何在不对依赖进行拍平的情况下又解决了依赖的空间占用问题呢。原因就是任何项目中的依赖都是本地依赖的一个硬链接,这个硬链接是几乎不占磁盘空间的。拿刚才的例子举例:

image.png

a, b, c 依赖的 d@1.0.0 都是对于公共 d@1.0.0 的一个硬链接。类比起来可以看作是一个快捷方式(软链接),因此只有一份 d@1.0.0 会真正的占用磁盘空间。