为什么推荐你使用pnpm

2024-05-09
344812 分钟
...

大家好,我是老纪。

pnpm是一种Node.js的包管理工具,用于帮助开发人员管理和安装项目所需的依赖项。它与其它包管理工具(如npmyarn)类似,但有一些不同之处。pnpm使用基于内容寻址的文件系统,来管理磁盘上依赖,使用硬链接和符号链接来共享依赖项,从而减少磁盘空间的使用量。它还支持并发安装,可以加快依赖项的安装速度。

总的来说,pnpm旨在提供更快速、更高效的包管理体验。

为什么不用npm和cnpm

Node.js的包管理工具,比较知名的有npm、yarn、pnpm、cnpm,像Deno、Bun这种底层另起炉灶的不计算在内。

这几个里,npm首先被排除出局。它唯一的优势是Node.js官方出品,但缺点非常明显:

  1. 安装速度慢。
  2. 安装过程不可靠。npm初始设计没有锁定依赖版本,这才有了yarn的出现。而同一项目中,同一个包不同版本的管理是个复杂的话题,npm也很努力地在解决各种问题,安装策略在不断调整,导致换个npm版本,可能项目就跑不起来了。朝令夕改,不向下兼容,这对于团队开发无疑是致命的。

cnpm是淘宝团队为了解决npm在国内下载速度慢的问题而开发的,它通过镜像加速了包的下载和安装过程,提供了更快的速度和稳定性。

由于我们团队有自己的npm私服,不能使用cnpm,这是根本原因。而cnpm有没有其它方面比如幽灵依赖、npm分身等优化,这块没有调研,不予置喙。

pnpm vs. yarn

pnpm和yarn几乎一同出道,都是2016年的作品,当然,它正式发布要稍晚些。

star

我们看下pnpm的star,已经有24.7K:

与yarn的41.2K还有段距离,但已经完全是同一数量级了。

更重要的是,Vue3已经迁移到了pnpm:

Vite也一样:

所以,从成熟角度讲,pnpm已经完全可以依赖,有什么坑,尤雨溪已经帮你踩过了。还有微软多年来已成功使用 pnpm 来管理大型 monorepo,有完整的实战实践。

全面对比

  1. 磁盘空间占用:相对于npm和yarn,pnpm在磁盘空间占用方面更加高效。pnpm使用了符号链接(symlink)来共享依赖项,因此每个依赖项只需要在磁盘上存储一次,可以节省大量的磁盘空间。
  2. 性能:pnpm和yarn都旨在提高包安装的性能。它们通过并行下载和使用本地缓存来加快安装速度。根据不同的项目和网络条件,它们的性能可能有所不同。由于pnpm使用硬链接,在已有缓存的情况下,它的安装速度会更快。
  3. 版本控制:yarn和pnpm都支持锁定文件来确保在不同环境中安装相同的依赖项版本。它们都会生成一个 yarn.lockpnpm-lock.yaml 文件,以记录确切的依赖项版本。这有助于确保项目的构建和部署的一致性。
  4. 幽灵依赖和npm分身:yarn采取平铺的node_modules结构,避免不了幽灵依赖和npm分身问题。而pnpm的链接机制,巧妙地解决了这两个顽疾。本文不详细阐述其中区别,有兴趣的看看《平铺的结构不是 node_modules 的唯一实现方式》和《npm分身》这两篇文章。
  5. 社区支持和生态系统:yarn是由Facebook开发并得到广泛使用的,因此拥有一个庞大的社区支持和生态系统。与此相比,pnpm的社区和生态系统相对较小。但随着Vue与Vite的入坑,双方差距已经可以忽略不计。

再来一个pnpm官方的对比表格:

功能pnpmYarnnpm
工作空间支持(monorepo)✔️✔️✔️
隔离的 node_modules✔️ - 默认✔️✔️
提升的 node_modules✔️✔️✔️ - 默认
自动安装 peers✔️✔️
Plug'n'Play✔️✔️ - 默认
零安装✔️
修补依赖项✔️✔️
管理 Node.js 版本✔️
有锁文件✔️ - pnpm-lock.yaml✔️ - yarn.lock✔️ - package-lock.json
支持覆盖✔️✔️ - 通过 resolutions✔️
内容可寻址存储✔️
动态包执行✔️ - 通过 pnpm dlx✔️ - 通过 yarn dlx✔️ - 通过 npx
Side-effects cache✔️
Listing licenses✔️ - Via pnpm licenses list✔️ - Via a plugin

以下是pnpm使用硬连接存储的示例图:

迁移

如果你已经有一个项目在使用npm或yarn,那么你想迁移到pnpm上,应该怎么做?

你可能会三下五除二,直接pnpm install开干,那样就丧失了原来lock文件记录的版本优势。最佳姿势是使用pnpm import:

 pnpm import yarn.lock
 pnpm import package-lock.json

以我某个工程为例:

生成一个pnpm-lock.yaml文件:

lockfileVersion: '6.0'

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false

dependencies:
  arg:
    specifier: 5.0.0
    version: 5.0.0
  chalk:
    specifier: 4.1.1
    version: 4.1.1

pnpm可谓是非常贴心了。

之后删除node_modulesyarn.lock,再用pnpm i安装依赖即可。

旧的项目这样安装完依赖,运行时仍有可能报错,缺失某个包,这是因为pnpm的包安装策略与yarn、npm都有所差别。这时缺哪个包,就安装哪个包,大部分情况就OK了。

特性功能

pnpm完全兼容npm的功能,常规命令如安装依赖、钩子等就不提了,pnpm官网上都有。这里说几个比较有意思的。

打补丁

打补丁是个非常实用的功能。我在《奇技淫巧:如何修改第三方npm包?》一文中已经说的很详细了,这里就不重复了。

管理Node.js版本

神奇的是,pnpm居然还能管理Node.js版本,这有点儿抢nvm、n饭碗的意思。

不过它们之间是有冲突的,原理很简单,使用Node.js哪个版本是看哪个脚本的优先级高,比如我机器的nvm.zshrc中是这样的:

export NVM_DIR="$HOME/.nvm"
  [ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"  
  [ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"  

这时Node.js的版本就由nvm控制了:

jw@MacBook ~ % node -v
v16.16.0
jw@MacBook ~ % which node
/Users/jw/.nvm/versions/node/v16.16.0/bin/node

但当使用了pnpm管理后,.zshrc中多了一段:

export PNPM_HOME="/Users/jw/Library/pnpm"
case ":$PATH:" in
  *":$PNPM_HOME:"*) ;;
  *) export PATH="$PNPM_HOME:$PATH" ;;
esac

再看Node.js版本又由pnpm接管了:

jw@MacBook ~ % node -v
v18.17.0
jw@MacBook ~ % which node
/Users/jw/Library/pnpm/node

pnpm安装Node.js很简单:

pnpm env use --global lts
pnpm env use --global 16

移除:

pnpm env remove --global 14.0.0

列表:

pnpm env list
pnpm env list --remote
pnpm env list --remote 16

可以在.npmrc文件中强制指定Node.js版本:

use-node-version=18.14.2
node-mirror:release=https://npmmirror.com/mirrors/node/
node-mirror:rc=https://npmmirror.com/mirrors/node-rc/
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/

安全检查

检查已安装包的已知安全问题,可以执行pnpm audit进行安全检查。其实这个命令是npm就有,算不上特色。因为用淘宝镜像不行,需要指定官方源:

pnpm audit --registry=https://registry.npmjs.org 

这是一个输出样例:

如果发现安全问题,请尝试通过 pnpm update 更新依赖项。

一定要指定具体包,千万别全量更新,相信我,如果不是新的工程,你十有八九没有这个好运气。

如果简单的更新不能解决所有问题,请使用 overrides来强制使用不易受攻击的版本。

例如,如果 lodash@<2.1.0 易受攻击,可用这个overrides来强制使用 lodash@^2.1.0:

{
    "pnpm": {
        "overrides": {
            "lodash@<2.1.0": "^2.1.0"
        }
    }
}

或者,运行 pnpm audit --fix

pnpm audit --fix --registry=https://registry.npmjs.org 

如果你想容忍一些不影响项目的漏洞,可以使用 pnpm.auditConfig.ignoreCves 设置。

工作空间

pnpm 内置了对单一存储库(也称为多包存储库、多项目存储库或单体存储库,也就是monorepo项目)的支持, 你可以创建一个workspace,将多个项目合并到一个仓库中。

本文开篇Vue3、Vite的仓库截图都能看到一个文件 pnpm-workspace.yaml。这是Vite工程的文件内容:

packages:
  - 'packages/*'
  - 'playground/**'

package.json中:

"scripts": {
    "build": "pnpm -r --filter='./packages/*' run build",
    "dev": "pnpm -r --parallel --filter='./packages/*' run dev",
    "release": "tsx scripts/release.ts",
    "ci-publish": "tsx scripts/publishCI.ts"
},
"devDependencies": {
   "vite": "workspace:*"
}

这是packages下的子工程:

打开packages/plugin-legacy/package.json,可以看到scripts的内容:

"scripts": {
  "dev": "unbuild --stub",
  "build": "unbuild && pnpm run patch-cjs",
  "patch-cjs": "tsx ../../scripts/patchCJS.ts",
  "prepublishOnly": "npm run build"
}

这几个命令都是父工程可以调用的,比如父工程的release,是用来发布版本用的,脚本里会触发子工程的publish,也就是上面的prepublishOnly钩子:

async function publishPackage(pkdDir, tag, provenance) {
  const publicArgs = ["publish", "--access", "public"];
  if (tag) {
    publicArgs.push(`--tag`, tag);
  }
  if (provenance) {
    publicArgs.push(`--provenance`);
  }
  await runIfNotDry("npm", publicArgs, {
    cwd: pkdDir
  });
}

这段代码出自@vitejs/release-scripts。有意思的是,按理说这个包应该属于Vite这个工程的一部分,现在却是独立的,在包的package.json里也找不到代码仓库,不知道出于什么考虑。看历史提交记录是github.com/vitejs/vite…,或许是觉得放当前仓库里有鸡生蛋蛋生鸡的哲学拷问?

pnpm的存储特性,使得它构建monorepo项目更有优势。还一个有趣的发现是Vue与Vite都没有采用市面上流行的monorepo工具,如Rush、Nx或者Lerna,而是自己直接基于pnpm写了套脚本。这也侧面说明pnpm工作空间的价值,只需要少量的脚本,就能搭建一个不错的monorepo项目。

总结

本文简单介绍了pnpm的优点,介绍了如何将项目从npm、yarn迁移,以及一些特性功能,比如补丁、管理Node.js版本、安全检查和工作空间。随着Vue社区开始拥抱pnpm,相信pnpm将会变得越来越受欢迎。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
pnpm2026-03-17
Bun 为什么比 pnpm(以及 npm、Yarn)快
Bun 之所以比 pnpm(以及 npm、Yarn)快,核心原因在于其 底层架构的彻底重构 。pnpm 虽然通过硬链接和内容寻址解决了磁盘空间和部分安装效率问题,但它本质上仍是一个运行在 Node.js 之上的 JavaScript 工具;而 Bun 是从底层用 Zig 语言 编写的原生二进制程序,并深度集成了 JavaScriptCore 引擎。 以下是 Bun 比 pnpm 快的具体技术原因: 1\. 编程语言与执行环境的差异(最根...
学习
pnpm2025-09-01
📦 npm、yarn、pnpm、bun 是什么
🎯 什么是包管理器? 在 JavaScript 和 Node.js 生态中,一个项目往往依赖很多第三方库,比如 Vue、React、Lodash 等。\ \ 包管理器(Package Manager)\ \ 的作用,就是: 安装/卸载这些依赖库; 管理依赖的版本; 构建依赖树; 提供运行脚本(如 npm run dev)等功能。 最早是 npm,后来有了性能更好的 yarn,又有了极致节省空间的 pnpm,最近还杀出了一个全能选手 —...
学习
pnpm2024-09-29
【npm私有化部署】手把手教你使用Verdaccio搭建私有化npm
一、Verdaccio是什么? 是一个 Node.js创建的轻量的私有npm代理注册源(proxy registry) 通过Verdaccio搭建私有npm服务器有着以下优势: 1. 零配置:无需安装数据库,基于 ,安装及运行。 2. 使用方便:将内部高复用的代码进行提取,方便在多个项目中引用。 3. 安全性:仓库搭建在局域网内部,只针对内部人员使用。 4. 权限管理:对发布和下载npm包配置权限管理。 5. 加速包下载:将下载过的依赖...
学习
pnpm2024-09-11
NPM私库搭建-verdaccio(Linux)
linux服务器安装node a)、官网下载所需的node版本 b)、解压安装包 若下载的是xxx.tar.xz文件,解压命令为 c)、修改环境变量 修改:/etc/profile文件 d)、环境变量需重启生效 e)、查看当前node版本 防止报grywarn权限的错 是因为原来的私库崩了 这里直接指明仓库地址为淘宝镜像 文件位置: 如果找不到该文件,需要先运行verdaccio,直接执行verdaccio命令即可。 继续修改配置文件:...
学习
pnpm2024-09-02
【npm进阶】探索npx:一次性执行包的新方式
在现代前端开发中,开发者们需要管理和使用各种各样的npm包。而在此过程中, 作为npm生态系统中的一部分,提供了一种方便的方式来执行本地或远程的npm包。本篇文章将详细介绍 的用途和优势,展示如何使用 来执行本地和远程包,并提供一些实际的npx示例。 引言 在过去,开发者通常会通过全局安装某些工具来使用它们。然而,全局安装不仅会污染全局环境,还可能导致不同项目之间的版本冲突。 是npm 5.2.0引入的一种新工具,旨在解决这些问题。通过...
学习
pnpm2024-07-10
外网向内网导包的坎坷之旅
最近工作遇到个问题,一开始项目是在外网运行的,后来需要导入内网运行,我一开始是把node modules整个目录拷贝过去了,结果运行不起来,可能因为项目是pnpm的,直接从mac拷贝到windows似乎是运行不起来。然后尝试自己下载,缺了好多包,也启动不了运行不起来,最后只好开始了我的导包之旅。 这是一个悲伤的故事,前前后后和对应的人员来回导包4、5次,历时几周才搞完,因为项目不着急,所以就没有急着去搞,真是尴尬。下面就是我的导包之旅:...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录