大家好!
我相信你已经看到过Node.js 项目使用不同的包管理器,例如:
我自己也见过这种情况,也使用过上述所有方法,但我总是有一个问题 - 是什么驱使人们/团队使用yarn或pnpm而不是npm ? 优点是什么? 有什么缺点吗?
好吧...让我们来一探究竟!
我决定从“速度”的角度比较一下npm 、 yarn和pnpm ……
您将看到以下 3 个指标:
生成没有任何缓存的锁文件。
从现有锁文件安装依赖项,无需任何缓存。
使用全局缓存从现有锁文件安装依赖项。
缓存有两种类型:
全球的。
通常存储在用户的主目录(fe, ~/.yarn/berry/cache
)中。
当地的。
存储在项目目录(fe, <project-dir>/.yarn
)。
虽然根据我的经验, #2和#3是最常见的用例,但为了以防万一,我也选择了#1 (尽管这是一种非常罕见的情况)。
我使用了create-react-app中的示例项目作为基准测试的示例。
它是Node.js生态系统的默认包管理器 - 还有什么可说的?它附带安装包,因此当您在计算机上安装Node.js时(或在任何CI提供商中,如果您在那里设置了Node.js ),它基本上就可以使用了。
在我看来,这是一个巨大的“优点”——您不需要单独安装它!
没什么特别突出的——它只是……有用!多年来我没有看到任何重大错误——它似乎相当稳定,可以完成工作。
我目前使用过的npm的功能:
npm将依赖项存储在项目根目录的node_modules
文件夹中。非常简单。
ℹ️ package-lock.json
存储有关列出的软件包的注册表信息 - 如果你从单一范围获得软件包,它会非常方便,即不同注册表中的@example-company
(例如 - npm和GitHub Packages ):
现在,让我们看看它的安装速度如何……
花了package-lock.json
并安装依赖项,而无需任何缓存。
使用的命令:
npm i
花了package-lock.json
安装依赖项而无需任何缓存。
使用的命令:
npm ci
花了package-lock.json
安装依赖项。
使用的命令:
npm ci
我能够创建一个工作区并同时管理整个工作区的依赖关系以及单独管理特定项目的依赖关系。
换句话说 - 它可以毫无错误/问题地完成工作,并且官方文档非常简单明了。
我目前使用过的工作区功能:
说实话,我还没有尝试过yarn的一些功能。我的意思是,我在处理一些项目时经常使用“安装依赖项”功能,仅此而已。
yarn不附带Node.js安装程序,因此您必须单独安装它。这意味着您的CI管道中会有一个额外的步骤 - 您必须在安装项目依赖项之前设置yarn 。
yarn有两种安装依赖项的方法:
“零安装”(默认) - 创建.yarn
文件夹并列出yarn.lock
和.pnp.cjs
文件中的包。
常规的 - 类似于npm ,将依赖项存储到node_modules
中并将它们列在yarn.lock
文件中。
ℹ️仅当您使用旧的(常规)安装方法时, yarn lock 文件才会存储有关所有列出的包的注册表的信息。
⚠️请记住,“零安装”似乎将包存储在本地缓存中并提供指向锁定文件的链接:
如果您有一个Dockerfile或CI管道,您可以在一个干净的环境中安装依赖项,然后想要将其移动到另一个环境(您必须复制.yarn
文件夹和本地缓存),那么这对您来说可能很重要。
由于 yarn 现在的默认方法是“零安装”,并且比旧方法具有更好的性能 - 我们将仅使用这种方法来记录基准。
花了yarn.lock
文件并安装依赖项(无缓存)。
使用的命令:
yarn install
花了
使用的命令:
yarn install --frozen-lockfile
花了
使用的命令:
yarn install --frozen-lockfile
我能够创建一个工作区并同时管理所有项目的依赖关系以及分别管理特定项目的依赖关系。
我目前使用过的工作区功能:
文档很好,但是命令名称和标志有点令人困惑。
例如,我必须执行此操作才能在根目录( . ) 和嵌套的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 为多个依赖项中使用的包创建符号链接。例如,如果包a
和b
使用包c
作为依赖项 - pnpm将把包c
存储为单个副本并为包a
和b
创建符号链接。这样,包管理器就不会创建硬拷贝并节省 SSD/HDD 上的内存。
ℹ️ pnpm-lock.yaml
不存储有关列出的包的注册表的信息。
⚠️请记住, pnpm有时会将依赖项存储在全局缓存中,而不是将其保存在项目中。
花了pnpm-lock.yaml
并安装依赖项,无需任何缓存。
使用的命令:
pnpm install
花了pnpm-lock.yaml
安装依赖项(无需缓存)。
使用的命令:
pnpm i --frozen-lockfile
花了pnpm-lock.yaml
安装依赖项。
使用的命令:
pnpm i --frozen-lockfile
现在,事情变得非常有趣了……
pnpm有很多配置选项,但一些核心功能根本不起作用!
让我们回顾一下我遇到的几个错误:
能够仅为特定项目安装依赖项非常重要——当您在工作区内创建与特定项目相关的管道时,它对于 monorepos 非常有用。
即,想象一下你的工作区中有:
所有这些都是独立的npm项目,但它们是同一个 repo 的一部分☝️
现在,您希望管道仅运行端到端测试。因此,您只需要端到端测试依赖项,对吗?
好吧,您将无法做到这一点 - pnpm强制您为整个工作区安装依赖项!
pnpm install --filter <project-name>
本来应该仅为选定的项目安装依赖项,但它不起作用。
这里有一个一年前的错误,最近它被关闭了,并且没有提供任何有效的修复程序。
当你运行pnpm install
时, pnpm默认会为整个工作区(所有项目)安装依赖项
如果在工作区根目录的.npmrc
中设置recursive-install=false
则可以改变此行为。
pnpm默认将依赖项列表存储在单个锁文件中(与npm和yarn相同)。
如果您在工作区根目录的.npmrc
中设置shared-workspace-lockfile=false
您也可以改变这种行为。
这将允许我们保留工作区功能并使用--ignore-workspace
标志来为特定项目安装依赖项。
无论如何,这种设置会带来更多问题:
eslint
和tsc --noEmit
在我的GitHub Actions管道中抛出“JavaScript 堆内存不足”错误。
一些依赖项存储在全局缓存中,并在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没有附带开箱即用的Node.js安装程序,但可以使用corepack或现有操作轻松地在 CI 管道中进行设置。
我更喜欢npm ,因为:
package-lock.json
中,以便您能够从不同的注册表安装具有单一范围的依赖项。
这些优点比我使用yarn或pnpm所节省的速度和磁盘空间更重要。
你选择包管理器的标准是什么?别害羞,在下面的评论部分告诉我你的想法!👇😊