paint-brush
为什么你不需要 PNPM 和 YARNby@bormando

为什么你不需要 PNPM 和 YARN

Dmitrii Bormotov9m2024/07/23
Read on Terminal Reader

npm 是 **Node.js** 生态系统的默认包管理器。它附带安装包,因此当您在计算机上安装 **Node.js** 时(或在任何 **CI** 提供商中,如果您在那里设置了 **Node.js**),它基本上就可以使用了。它很稳定,速度与其他包管理器相当。
featured image - 为什么你不需要 PNPM 和 YARN
Dmitrii Bormotov HackerNoon profile picture
0-item

大家好!


我相信你已经看到过Node.js 项目使用不同的包管理器,例如:



我自己也见过这种情况,也使用过上述所有方法,但我总是有一个问题 - 是什么驱使人们/团队使用yarnpnpm而不是npm ? 优点是什么? 有什么缺点吗?


好吧...让我们来一探究竟!

性能比较规则

我决定从“速度”的角度比较一下npmyarnpnpm ……


您将看到以下 3 个指标:


  1. 生成没有任何缓存的锁文件。


  2. 从现有锁文件安装依赖项,无需任何缓存。


  3. 使用全局缓存从现有锁文件安装依赖项。


缓存有两种类型:


  1. 全球的。

    通常存储在用户的主目录(fe, ~/.yarn/berry/cache )中。


  2. 当地的。

    存储在项目目录(fe, <project-dir>/.yarn )。


虽然根据我的经验, #2#3是最常见的用例,但为了以防万一,我也选择了#1 (尽管这是一种非常罕见的情况)。


我使用了create-react-app中的示例项目作为基准测试的示例。

npm

它是Node.js生态系统的默认包管理器 - 还有什么可说的?它附带安装包,因此当您在计算机上安装Node.js时(或在任何CI提供商中,如果您在那里设置了Node.js ),它基本上就可以使用了。


在我看来,这是一个巨大的“优点”——您不需要单独安装它!


没什么特别突出的——它只是……有用!多年来我没有看到任何重大错误——它似乎相当稳定,可以完成工作。


我目前使用过的npm的功能:


  1. 管理依赖项(安装、删除、更新)
  2. 发布包(私有、公共)
  3. 链接本地包
  4. 管理工作区。

管理依赖关系

npm将依赖项存储在项目根目录的node_modules文件夹中。非常简单。


ℹ️ package-lock.json存储有关列出的软件包的注册表信息 - 如果你从单一范围获得软件包,它会非常方便,即不同注册表中的@example-company (例如 - npmGitHub Packages ):


package-lock.json 条目


现在,让我们看看它的安装速度如何……

生成不带任何缓存的 package-lock.json

生成 package-lock.json 并安装依赖项,无需任何缓存


花了1分钟npm生成package-lock.json并安装依赖项,而无需任何缓存。


使用的命令:

 npm i

从 package-lock.json 安装依赖项,无需任何缓存

从 package-lock.json 安装依赖项,无需任何缓存


花了18 秒npmpackage-lock.json安装依赖项而无需任何缓存。


使用的命令:

 npm ci

使用全局缓存从 package-lock.json 安装依赖项

使用全局缓存从 package-lock.json 安装依赖项


花了8 秒npm使用全局缓存从package-lock.json安装依赖项。


使用的命令:

 npm ci

管理工作区

我能够创建一个工作区并同时管理整个工作区的依赖关系以及单独管理特定项目的依赖关系。


换句话说 - 它可以毫无错误/问题地完成工作,并且官方文档非常简单明了。


我目前使用过的工作区功能:


  1. 为工作区内的所有项目安装依赖项。
  2. 为单个特定项目安装依赖项。
  3. 一次性递归地为所有项目运行单一脚本。

说实话,我还没有尝试过yarn的一些功能。我的意思是,我在处理一些项目时经常使用“安装依赖项”功能,仅此而已。


yarn不附带Node.js安装程序,因此您必须单独安装它。这意味着您的CI管道中会有一个额外的步骤 - 您必须在安装项目依赖项之前设置yarn

管理依赖关系

yarn有两种安装依赖项的方法:


  1. 零安装”(默认) - 创建.yarn文件夹并列出yarn.lock.pnp.cjs文件中的包。


  2. 常规的 - 类似于npm ,将依赖项存储到node_modules中并将它们列在yarn.lock文件中。


ℹ️当您使用旧的(常规)安装方法时, yarn lock 文件才会存储有关所有列出的包的注册表的信息。


⚠️请记住,“零安装”似乎将包存储在本地缓存中并提供指向锁定文件的链接:


软件包链接

如果您有一个DockerfileCI管道,您可以在一个干净的环境中安装依赖项,然后想要将其移动到另一个环境(您必须复制.yarn文件夹和本地缓存),那么这对您来说可能很重要。


由于 yarn 现在的默认方法是“零安装”,并且比旧方法具有更好的性能 - 我们将仅使用这种方法来记录基准。

生成不使用任何缓存的锁定文件

使用yarn生成锁文件并安装依赖项


花了16.5 秒yarn生成一个yarn.lock文件并安装依赖项(无缓存)。


使用的命令:

 yarn install

从现有锁定文件安装依赖项而不使用任何缓存

使用“零安装”方法安装依赖项,无需任何缓存


花了11 秒使用yarn 的“零安装”方式安装依赖项,并且不包含任何缓存。


使用的命令:

 yarn install --frozen-lockfile

使用全局缓存从现有锁定文件安装依赖项


使用“零安装”方法和全局缓存安装依赖项


花了8 秒使用“零安装”方法和全局缓存来安装yarn依赖项。


使用的命令:

 yarn install --frozen-lockfile

管理工作区

我能够创建一个工作区并同时管理所有项目的依赖关系以及分别管理特定项目的依赖关系。


我目前使用过的工作区功能:


  1. 为工作区内的所有项目安装依赖项。
  2. 为单个特定项目安装依赖项。
  3. 一次性递归地为所有项目运行单一脚本。


文档很好,但是命令名称和标志有点令人困惑。


例如,我必须执行此操作才能在根目录( . ) 和嵌套的b2b项目中运行test脚本:


 yarn workspaces foreach -A --include '{.,b2b}' run test


npm相比:


 npm run test --workspace=b2b --include-workspace-root

韓國

pnpm目前正受到热捧——许多公司和开源项目都在使用它


就像yarn一样 - pnpm不附带Node.js安装程序,因此您必须单独安装它。这意味着您的CI管道中将有一个额外的步骤 - 您必须在安装项目依赖项之前设置pnpm

管理依赖关系

pnpm被认为是快速、节省磁盘空间的包管理器 ……


确实,我同意在本地管理依赖项方面的“磁盘空间高效”说法。


默认情况下, pnpm会删除共享依赖项的重复。pnpm 为多个依赖项中使用的包创建符号链接。例如,如果包ab使用包c作为依赖项 - pnpm将把包c存储为单个副本并为包ab创建符号链接。这样,包管理器就不会创建硬拷贝并节省 SSD/HDD 上的内存。


ℹ️ pnpm-lock.yaml不存储有关列出的包的注册表的信息。


⚠️请记住, pnpm有时会将依赖项存储在全局缓存中,而不是将其保存在项目中。

生成无任何缓存的 pnpm-lock.yaml

生成 pnpm-lock.yaml


花了31 秒pnpm生成pnpm-lock.yaml并安装依赖项,无需任何缓存。


使用的命令:

 pnpm install

从 pnpm-lock.yaml 安装依赖项(不使用全局缓存)

从 pnpm-lock.yaml 安装依赖项,无需全局缓存


花了16 秒pnpmpnpm-lock.yaml安装依赖项(无需缓存)。


使用的命令:

 pnpm i --frozen-lockfile


使用全局缓存从现有锁定文件安装依赖项

使用缓存从 pnpm-lock-yaml 安装依赖项


花了5秒pnpm使用全局缓存从pnpm-lock.yaml安装依赖项。


使用的命令:

 pnpm i --frozen-lockfile

管理工作区

现在,事情变得非常有趣了……


pnpm有很多配置选项,但一些核心功能根本不起作用!


让我们回顾一下我遇到的几个错误:

pnpm 安装--filter

能够仅为特定项目安装依赖项非常重要——当您在工作区内创建与特定项目相关的管道时,它对于 monorepos 非常有用。


即,想象一下你的工作区中有:


  • 一个网络应用程序,
  • 后端服务器,
  • 测试项目(端到端测试)。


所有这些都是独立的npm项目,但它们是同一个 repo 的一部分☝️


现在,您希望管道仅运行端到端测试。因此,您只需要端到端测试依赖项,对吗?


好吧,您将无法做到这一点 - pnpm强制您为整个工作区安装依赖项!


pnpm install --filter <project-name>本来应该仅为选定的项目安装依赖项,但它不起作用。


这里有一个一年前的错误,最近它被关闭了,并且没有提供任何有效的修复程序。

递归安装=false

当你运行pnpm install时, pnpm默认会为整个工作区(所有项目)安装依赖项


如果在工作区根目录的.npmrc中设置recursive-install=false则可以改变此行为。


它引入了另一个已存在近两年的错误

共享工作区锁文件=false

pnpm默认将依赖项列表存储在单个锁文件中(与npmyarn相同)。


如果您在工作区根目录的.npmrc中设置shared-workspace-lockfile=false您也可以改变这种行为。


这将允许我们保留工作区功能并使用--ignore-workspace标志来为特定项目安装依赖项。


无论如何,这种设置会带来更多问题:


  1. eslinttsc --noEmit在我的GitHub Actions管道中抛出“JavaScript 堆内存不足”错误。


  2. 一些依赖项存储在全局缓存中,并在node_modules/.pnpm中符号链接。

性能比较结果

#

npm

韓國

生成锁文件

60 秒

16.5 秒

31 秒

不使用任何缓存来安装依赖项

18 秒

11 秒

8 秒

使用全局缓存安装依赖项

8 秒

8 秒

5 秒


根据上述基准测试, npm是最慢的包管理器☝️


无论如何,让我们解释一下这些结果……

生成锁文件

这种情况很少见。通常,锁定文件是在项目初始化时创建的,然后在安装/更新软件包时展开。


考虑到这一点 - 当你选择包管理器时,这似乎并不是一件非常重要的事情。

安装依赖项

大多数情况下,您的项目都会保留一个特定的依赖项列表,并且您很少添加/删除某些内容。


最有可能的是,您会不时地更新软件包的版本 - 这些变化很小,您会重新使用缓存中的其余软件包。


换句话说,常见的用例是——从包注册表中获取新包并从缓存中获取其余包。


pnpm (5-8 秒)几乎是npm (8-18 秒)的两倍,中间是yarn (8-11 秒)。

结论

事实

  • pnpm确实是“快速且节省磁盘”的包管理器——在当前评论中这一点很清楚!


  • pnpm工作区功能存在缺陷,其中一些缺陷多年来一直未解决。


  • pnpmyarn都需要在 CI 管道中进行额外设置,而npm 则不需要。


  • pnpmyarn都不会在其锁文件中存储包注册信息,而npm则会存储。

作者的想法

我认为,如果您对包管理器的要求只是“仅安装依赖项”,那么pnpm是最好的选择。


尽管pnpm没有附带开箱即用的Node.js安装程序,但可以使用corepack现有操作轻松地在 CI 管道中进行设置。


我更喜欢npm ,因为:


  • 它很稳定(尤其是工作空间),


  • Node.js提供,不需要在 CI 管道中进行额外设置,


  • 将包注册表存储在package-lock.json中,以便您能够从不同的注册表安装具有单一范围的依赖项。


这些优点比我使用yarnpnpm所节省的速度和磁盘空间更重要。


你选择包管理器的标准是什么?别害羞,在下面的评论部分告诉我你的想法!👇😊