为什么我选择了pnpm?

1,972 阅读5分钟

首先说一下 pnpm 是 nodejs 的包管理工具,主打特性就是高性能的npm,官网地址:pnpm.io/

其实在pnpm刚出之前,我也尝试过pnpm,但是后续也不了了之,并没有深入,因为pnpm作为新的包管理工具,在npm和yarn的霸主级别的统治下,无论是功能丰富度,社区生态,都很难抗衡,但是最近优化内部项目平台中因为某些问题,尝试了pnpm内置的monorepo,可以说效率确实高,也就从yarn+lerna转到了pnpm,pnpm得到了实践和落地,总的来说pnpm确实优秀,后来者居上。

那本文就将介绍一下pnpm的一些特性,为什么我选择了pnpm?

pnpm有多快?

这张图是官网的基准测试中的图,相信也是一图胜千言,无论是什么组合,pnpm的速度都是极快的。

image.png

平铺依赖管理不是 node_modules 唯一的解法

在我们接触node的时候,npm就是我们学习的包管理器,而在npm v1和 v2中,包是通过嵌套结构进行管理的,这种管理模式是有问题的,比如:

  • 依赖无法被共用
  • 依赖层级太深,导致文件路径过长

所以yarn则采用了平铺的方式,解决了上面的问题,也让node_modules减少了尺寸,但也随之带来了新的问题:

  • Phatom dependencies(幽灵依赖)

依赖管理在 yarn 和 npm v3+ 中都是使用平铺的方式,大家平时看项目的 node_modules 也应该比较熟悉,所以yarn对npm的管理是兼容的,而平铺的依赖树会带来很多问题,其中最经典的问题就是模块可以访问它们不依赖的包,就是package.json没有的包,我也能引入。

比如说项目中依赖了A,A依赖了B,在package.json,只有A,但由于扁平管理那么在node_modules就可以看到同级的A、B:

▾ node_modules
    ▸ AB

这样通过 nodejs 的require的寻址特性,我们就可以直接找到B,这一点我之前也利用过这个缺陷去减少了部分依赖,但后续出现过A真的不依赖B了,也就不再用这个缺陷了。

而pnpm的依赖管理是采用了网状 + 平铺的目录结构,pnpm install 后我们可以从本地项目的 node_modules 看到,实际上pnpm 是将包装在了 node_modules/.pnpm/xx 中 使用symlink的形式去软链在 node_modules 下。

详情可以看看这篇文章:pnpm.io/zh/blog/202…

在这篇文章中介绍了 pnpm 目前的 node_modules 的一些文件结构,例如在项目中使用 pnpm 安装了一个叫做 express 的依赖,那么最后会在 node_modules 中形成这样类似这样的目录结构(省略非express依赖目录):

▾ .pnpm
    ▾ express@4.17.3
.modules.yaml
▾ express
    ▸ lib
      History.md
      index.js
      LICENSE
      package.json
      Readme.md

在上面的结构当中,我们可以看到 express 是没有nodemodules的,实际上这个文件只是个软连接,它会形成一个到第二个目录的一个软连接(类似于我们放在桌面的快捷方式),这样 node 在找路径的时候,最终会找到 .pnpm 这个目录下的内容。

通过这个形式,pnpm能够很好的进行包的隔离,所以对不同版本的依赖也有着极其严格的区分要求。

Monorepo 支持

我们先看传统方案,比较常用的monorepo管理工具有lerna和yarn workspace,这些monorepo管理工具主要解决,在一个仓库中管理多个package遇到的问题:

  • 相同的第三方依赖都各自装在各个package中,让整个项目的体积增大
  • 模块之间的link

虽然解决了上面的问题,也带来更严重的 Phatom dependencies,而且yarn和lerna配置麻烦,特别是单独使用和两者混合使用的区别,我想刚入门的同学就很难搞懂了。

而pnpm 在 monorepo 场景可以说算得上是个完美的解决方案了,因为其本身的设计机制,导致很多关键或者说致命的问题都得到了相当有效的解决。

pnpm内置了对monorepo的支持,只需在工作空间的根目录创建pnpm-workspace.yaml和.npmrc配置文件,同时还支持多种配置,相比较lerna和yarn workspace,pnpm解决monorepo的同时,也解决了传统方案引入的问题。

hard link(硬链接)

前面提到了pnpm的在引用依赖的时候是通过 symlink 去找到对应虚拟磁盘目录下(.pnpm 目录)的依赖地址, 那仅仅这些还不足以让pnpm得到如此大的性能提升,pnpm还利用了计算机里面一个叫做 Hard link 的机制。

例如项目里面有个 1MB 的依赖 a,在 pnpm 中,看上去这个 A 依赖同时占用了 1MB 的 node_modules 目录以及全局 store 目录 1MB 的空间(加起来是 2MB),但因为 hard link 的机制使得两个目录下相同的 1MB 空间能从两个不同位置进行寻址,因此实际上这个 A 依赖只用占用 1MB 的空间,而不是 2MB。

具体可以查看这篇文章:pnpm.io/faq#why-doe…

总结

关于 pnpm 的优点不用说太多,而且pnpm作者本人也努力的在完善 pnpm 的 feature 以及规划未来的发展方向,天生的优势让它简单的高效率支持了 workspace monorepo,搭建非常的简单,如果只需要项目间共享也可以,也不与git绑定。

但pnpm 不是银弹,任何新事物都需要多面的去看待,比如pnpm 关于 peerdependencies 的问题也正是因为其严格的隔离产生的,这里也不多阐述。