《JavaScript包管理器:npm VS yarn VS pnpm》翻译

206 阅读8分钟

最近在前端学习的过程中遇到了一个peerDependency的问题,但是苦于对包管理器的使用还局限于安装启动的水平,因此先通过一篇文章来进行三个包管理器的进一步了解。先贴地址:JavaScript Package Managers: NPM Vs YARN Vs PNPM

首先来看什么是包管理器?就是一个帮助开发者来安装、更新以及卸载代码库或者其他软件包的软件工具。 目前多种语言都有其对应的包管理工具,比如JS(JavaScrip)t的npm以及Ruby的gem。

包管理器通常使用元数据来确定可用的包版本,并且每个版本包管理器的依赖关系因它们安装和更新的自动化软件的类型而异。比如apt-get, DNF (Fedora/Red Hat), Pacman (Arch Linux),  以及npm (Node. js)。

任何用户都可以向这个公共池内添加或者删除包,包通常是在远端服务器内进行检索,但是同样也可以安装到本地。

什么是包管理器

包管理器就是一个对电脑软件包进行安装更新删除的软件,它在电脑硬盘或网络驱动的中心位置来存储包,允许多个用户来使用同一个包。包管理器,例如npm或者yarn,基本上都是基于CLI的。JS应用会依赖许多包,这些包就是由包管理器进行管理的。

npm是Node的默认包管理器,但是它并没有高级的功能来适配高级的应用,并且在安装依赖或者解决依赖关系时还会出现安装较慢的情况;yarn和tnpm则是社区版的用来解决以上问题的包管理器,在过去几年里,yarn的速度也在逐渐变慢,但是是当下最流行的包管理器;pnpm时包管理器中最晚出现的,在安装或者更新包时也是表现最好的。

npm、yarn以及npm的对比

npm是一个JS包管理器,最初是在Node.js项目开发,它的存在使得开发者们可以在不同项目中更便捷的分享代码,甚至在他们的项目中使用其他人的代码。

yarn是由FB开发的JS包管理器,它的特点就是快、可信、安全。

pnpm是一个建立在npm基础上的JS包管理器,主要是为了简化node应用中包的安装流程,它是npm的变种,与其前身遵循着相同的原则,却拥有了额外的功能和更强大的能力。

性能和磁盘效率

npm:相比于yarn和pnpm有点慢。

yarn:与npm相比,yarn使用相同的扁平化node modules目录,但是采用了并行安装,所以速度更快一点。

pnpm:pnpm比npm快3倍,并且因为同时采用冷缓存和热缓存,相比yarn也具有一定的速度优势。

pnpm只是链接全局存储中的文件,而yarn则是复制缓存中的文件。包的各个版本在盘中保存不会超过一次。

pnpm算法并没有使用扁平化依赖树,这也使得它更易于实现和维护,需要的计算也更少。

接下来看一下在npm3及以前使用的方法,它的嵌套是存在问题的,导致不得不为每个依赖它们的包都进行复制。

node_modules
└─ foo
   ├─ prop-types@15.5.0
   ├─ package.json
   └─ node_modules
      └─ bar1
         ├─ prop-types@15.6.2
         └─ package.json
      └─ bar2
         ├─ prop-types@15.6.2
         └─ package.json

与NPM相比,PNPM则通过硬链接和符号链接解决了这个问题。PNPM按符号链接对所有依赖项进行分组,但保留了所有依赖项。

node_modules
├─ foo -> .registry.npmjs.org/foo/1.0.0/node_modules/foo
└─ .registry.npmjs.org
   ├─ foo/1.0.0/node_modules
   |  ├─ bar -> ../../bar/2.0.0/node_modules/bar
   |  └─ foo
   |     ├─ index.js
   |     └─ package.json
   └─ bar/2.0.0/node_modules
      └─ bar
         ├─ index.js
         └─ package.json

相比于另外两种包管理器,pnpm可以省下大量空间。

安全性

npm:npm处理坏包的方式导致许多项目存在一定的安全漏洞。

yarn:yarn classic和yarn berry通过yarn.lock中存储的checksums来保证安全性,同时还会阻止恶意包的安装,一旦发现了错误,安装过程就会停止。

pnpm:类似yarn,pnpm同样采用了checksums,除此之外,pnpm还会在执行前检查代码的完整性。

monorepo支持程度

monorepo是将多个相关的代码目录存放在同一个目录下,来避免管理多个包。

npm:npm通过多个CLI命令来支持monorepo,但是相比其他包管理器,它并不支持高级的筛选以及多个工作区。

yarn:支持monorepo,也就是它的workspaces功能。在workspace可用之前,开发者只能使用第三方应用Lerna来进行多包项目中的包管理。

pnpm:pnpm是npm中幽灵引用问题的唯一解决方式,monorepo项目有时会遇到幽灵引用的问题,而pnpm在这方面具有一定的优势。

幽灵引用

定义:没有在中声明的依赖,但是在项目中依然可以被意外正确引用。例如:

// package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "main": "src/index.js",
  "dependencies": {
    "package1": "^3.0.4"
  }
}

// index.js
var package1 = require("package1")
var package2 = require("package2");  // ???
var package3 = require("package3")  // ???

这边就出现了奇怪的情况,package2和package3并没有引用,但是程序却成功的跑了起来。这是因为它们都是package1的依赖,而npm在安装package1时会将其依赖同样安装到node_modules下,这样就出现了这个看起来对开发者有利的情况。

然而在monorepo项目中呢?可能会出现不同包依赖不同版本的package2,还有一种情况是如果幽灵依赖是devDependencies中包的依赖,虽然在开发情况下可以正常运行,在生产情况下却不会包含,导致bug。

安装工作流程

如前所述,以上包管理器都必须先安装到本地并且进行持续化部署。

npm:世界上最大的包注册表之一,会与node.js一起安装,主要使用package.json和package.lock.json文件。

当需要下载包时,只需要打开终端并输入以下命令: javascript

npm install 包名

默认情况下,npm会创建一个名为package.json的文件,所有安装的文件在这里都会进行记录。

yarn:yarn的出现是为了解决npm中存在的问题,它提供了很多新功能,比如通过lock文件来锁定版本、缓存等等,这些功能npm后续也进行了实现,主要使用package.json和yarn.lock文件。

// 安装yarn
npm install -g yarn

// yarn安装包
yarn add 包名

pnpm:同样使用npm安装,使用与npm类似。

// 安装pnpm
npm install -g pnpm

// pnpm安装包
pnpm install 包名

项目结构

安装完成后,包管理器就会生成对应的文件,所有的重要元信息都会存储在package.json文件内。

npm:package.json文件和node_modules文件建立好后,开发者可以手动在根目录下添加一个.npmrc配置文件。

.
├── node_modules/
├── .npmrc
├── package-lock.json
└── package.

yarn:yarn会创建yarn.lock文件和node_modules文件,同样可以通过.yarnrc文件进行配置,yarn classic同样支持.npmrc配置文件。

.
├── .yarn/
│   ├── cache/
│   └── releases/
│       └── yarn-1.22.17.cjs
├── node_modules/
├── .yarnrc
├── package.json
└── yarn.lock

pnpm:与npm不同的是,pnpm不会创建一个扁平化的依赖树,在node_modules内,每个包都会有自己的node_modules,并且每个依赖项都会在package.json中精确指定。

在 npm 版本 3 之前,node_modules结构是可预测的。这种方法的问题是双面的:

● windows 经常遇到由依赖树太深的包引起的长目录路径问题

● 多次复制包以满足多重依赖

pnpm没有通过扁平化依赖书来解决这个问题,每个包的依赖项都在一个node_modules文件夹中,并且通过符号链接将依赖项组合在一起,因此目录树是扁平的。

package.json文件和node_modules文件夹同样会在使用pnpm安装包时生成,但是因为pnpm采用了内容可寻址存储方法,导致其结构和npm或者yarn时完全不同。

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

总结

目前几乎所有的包管理器都实现了功能对等,但是也存在着一定差异。

例如,虽然pnpm和npm具有一定的相似性,他们在管理依赖时却具有很大差异:pnpm在性能在空间上都具有很大优势。不久,yarn classic 可能会停止支持。

作为最新的包管理器竞争者,yarn berry pnp还没有充分发挥其潜力来彻底改变包管理领域。

依赖关系在JS中是很常见的,默认情况下,node使用npm来进行管理,虽然npm在高级功能方面存在一定缺失,但是在解决包依赖和安装包方面做得很好。


这篇文章对目前在JS开发中使用的三种包管理器进行了简单的介绍及对比,在翻译的过程中我也做了一定的增删改。如果要了解完成的使用命令,还是应该前往官网或者更详尽的介绍网站。