skip to content
usubeni fantasy logo Usubeni Fantasy

速通 npm、yarn、pnpm

/ 5 min read

npm<v3

node 依赖管理器的起源,在 npm 前两个版本中,依赖无限嵌套,各种重复,被调侃质量比黑洞还大。

yarn v1

yarn,代表 Yet Another Resource Negotiator。v1 版本现在被称为 yarn classic,当年拥有不少注目特性,DX(开发体验)↑ 安全 ↑ 安装速度 ↑,创新点:

  • 依赖提升(hoisting)、依赖安装扁平化
  • 引入 lock 文件
  • 有一些 npm 没有的命令,例如更新依赖到最新版本
  • 支持 monorepo
  • 离线缓存

但是引入了两个问题:

点击上面两个链接有详细解析,下面用中文总结几句。

依赖分身

依赖提升只能提升一个版本,其他版本依然安装在嵌套的 node_modules 中,会重复安装。所以如果项目里要求各种固定版本,依然是要安装一大堆重复的东西。

不过好消息是按照正常开发习惯和开发周期,下面这一大堆其实都只需要安装一次,以下是真实项目的 lock 文件的一部分:

lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.0:
version "4.17.21"
resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==

安装了一大堆不同版本的 lodash 其实都可以使用 4.17.21,它符合所有版本要求。不确定版本要求的写法可以在 npm semver calculator 查询。

幽灵依赖

因为依赖提升这个特性,非直接依赖的包你也能直接使用,因为全都平铺在顶层 node_modules,这种可以依赖你没直接依赖的包的情况就是幽灵依赖。

以前开发一个库的时候就遇到这个问题,开发的时候默认引入了某个工具,开发时一切正常,但是打包出来别人用的时候缺了一个依赖。

npm>=v3

npm3 之后的版本向 yarn 学习:

  • 跟进了 hoisting
  • 跟进了 lock 文件

How npm3 Works 比较详细地解析了 npm3 的改进。

然而……该有的问题还是没解决。

pnpm

pnpm 使用软硬链接的巧妙结合同时解决以上两个问题。

  • 所有依赖有一个全局仓库,被引用的会硬链(hard link)到项目
  • 对于依赖之间的关系,使用软链(Symlink)链接,实现最短路径查询依赖

官网给出这样的例子:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
├── foo@1.0.0
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
└── qar@2.0.0
└── node_modules
└── qar -> <store>/qar

<store>/ 的是硬,带 ../../ 的是软。即使依赖深度是 foo > bar > qar 依然可以保持这样的浅层结构。顶层只有一个 foo,没有幽灵依赖,项目用到的版本都在 .pnpm 平铺,不会造成依赖分身。

yarn>=v2

可以使用激进的 Plug’n’Play (PnP)

Yarn generates a single .pnp.cjs file instead of the usual node_modules folder containing copies of various packages.

PnP 不用 node.js 自己去找依赖位置了,直接用 .pnp.cjs 告诉它所有依赖的准确位置,这个准确位置是一个中央仓库,所以也不需要把依赖复制到项目的 node_modules 里。PnP 出现之初有很多工具都不支持这种依赖安装方法,现在兼容性估计好很多了。但是只要 pnpm 的速度还能接受,个人认为还是直接用传统 node_modules 吧。

对比

很惊讶,根据 JavaScript package managers compared: npm, Yarn, or pnpm? 中的性能测试结果,npm v8.1.2 已经比 yarn v1.23.0 快了,首次安装的速度 pnpm 仍然拉开两位前辈一大截。打破常规的 PnP 自然是最快的,但是要在项目中使用它,还得看项目能不能兼容 PnP。(虽然 pnpm 的软硬链有些包也不支持)

npm,yarn,pnpm performance

参考

评论组件加载中……