我去年年底帮一位朋友排查环境问题,他在 Windows 上新配了电脑,Node 装好、npm 也正常,结果一敲 pnpm 就弹出来那句经典的“不是内部或外部命令,也不是可运行的程序或批处理文件”。后面我远程看了一眼,不是他的问题,是绝大多数 pnpm 新手都会踩的路径和环境变量坑。这也让我意识到,虽然网上 pnpm 的资料不少,但真正能把“安装、配置、日常报错、卸载残留”这条完整链路讲透的文章其实不多。
这篇内容不打算从头复述官方文档,而是按我自己这些年从 npm 切到 pnpm 的真实使用经历来写。你会看到 pnpm 和 npm 到底差在哪、为什么装完总报“无法识别”、国内镜像怎么配才不折腾、approve-builds 这个新机制怎么理解,以及删除 pnpm 时那些“cli still installed”之类提示背后的原因。无论你是刚打算换包管理器的新手,还是已经被 pnpm 折磨过的老手,这篇都值得花十分钟从头看到尾。
1. 谈 pnpm 之前,先弄清楚它到底解决了什么问题
很多人刚开始接触 pnpm,只知道“它更快、更省磁盘”,但不知道快和省背后是有代价的,而这个代价对应的正是它最核心的设计思路。
1.1 传统 node_modules 的膨胀与幽灵依赖困局
如果你用过 npm 或 yarn 1.x,对 node_modules 一定不陌生。早期的 npm 采用嵌套安装,每个包都把依赖装在自己的子目录里,结果一个简单项目能装出几千个目录,路径深到 Windows 直接给你报“文件路径过长”。后来 npm 3 和 yarn 换成了扁平化 node_modules,把包提升到顶层,路径短了,但引入了“幽灵依赖”问题。
什么叫幽灵依赖?项目里明明只声明了 vue,结果 vue 内部依赖了 @vue/reactivity,npm 把后者也提升到了顶层 node_modules。于是你的业务代码里能直接 require('@vue/reactivity'),甚至不写进 package.json 都不会报错。这一天还好,等 vue 升级后不再依赖 @vue/reactivity,你的项目就莫名其妙崩了,排查起来非常痛苦。
我在公司维护过一个老项目,就是典型的重度幽灵依赖:根目录 package.json 里 30 多个依赖,结果 node_modules 顶层躺着 400 多个包。谁都不敢动依赖树,因为没人知道哪个包是“被幽灵”的。这个项目后来迁移到 pnpm,一次性暴露了十几个隐藏依赖,修复过程很痛苦,但修完心里踏实多了。
1.2 pnpm 的存储架构:内容寻址存储与硬链接
pnpm 解决这些问题的方案,是“内容寻址存储 + 硬链接 + 符号链接”。它有一个全局的 store,一般默认在用户目录下,比如 macOS 上是 ~/Library/Caches/pnpm,Windows 上是 %LOCALAPPDATA%\pnpm-cache。store 里的文件按内容哈希来命名,同一个文件不管被多少个项目引用,store 里只存一份。
当你执行 pnpm install 的时候,它并不会像 npm 那样把每个包复制一遍到项目里,而是从全局 store 硬链接到项目的 node_modules/.pnpm 目录,然后在项目根目录的 node_modules 里创建符号链接,指向 .pnpm 下对应的真实位置。你可以把 store 理解成一个“包的中心仓库”,每个项目只是往这个仓库开了个“快捷方式”。
这样做的好处有两个层面。第一层是磁盘空间,同一个版本同一个依赖,一百个项目也只占一份真实空间。第二层是依赖隔离,pnpm 的 node_modules 结构严格遵循 package.json 的声明,根目录只能看到你显式声明的依赖,其他一切都在 .pnpm 里被层层隔离,想碰都碰不到。这从根本上消灭了幽灵依赖。
1.3 和 npm、yarn 的关键差异对比
我整理了一张表,把 pnpm 和 npm、yarn classic 放在一起对比,方便你快速建立整体概念:
| 对比维度 | npm / yarn classic | pnpm |
|---|---|---|
| node_modules 结构 | 扁平提升,顶层混乱 | 符号链接 + 严格依赖树 |
| 磁盘占用 | 每个项目全量复制 | 全局 store 硬链接,多项目共享 |
| 幽灵依赖 | 常见 | 不允许 |
| 安装速度 | 较慢,锁文件解析一般 | 快,store 命中后几乎秒装 |
| 原生模块构建 | install 后自动跑 postinstall | pnpm 10 起默认阻断,需审批 |
| Monorepo 支持 | 需额外工具(lerna 等) | 内置 workspace 协议 |
你注意看表格里“原生模块构建”那一行,这是 pnpm 10 之后变化最大的点,后面我专门用一节来讲。现在你只需要记住一个结论:pnpm 带来安装速度和磁盘优势的同时,也会强制你的项目变得更干净、更规范。这是好事,但如果你从来没接触过这种严格的依赖模型,切到 pnpm 的时候必然会经历一段阵痛,这就是为什么网上会有那么多“pnpm 装完跑不起来”的求助帖。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 安装 pnpm 的完整路线:从“不是内部或外部命令”说起
这一节是搜索量最大的痛点区域。几乎每天都有新手在问 pnpm' 不是内部或外部命令,或者在 PowerShell 里看到 无法将"pnpm"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。先说结论:这类报错的本质是你安装了 pnpm,但终端找不到它的可执行文件,原因集中在 PATH 环境变量上。
2.1 “pnpm 不是内部或外部命令”的根因排查
在 Windows 上,这条报错跟“系统找不到指定的路径”基本是一个意思。你需要按顺序排查四件事。
第一,Node.js 和 npm 是否真的装好了。运行 npm -v,如果 npm 本身都报错,说明 Node 安装有问题,常见原因是安装时没有勾选“Add to PATH”。第二,pnpm 是否真的装上了。运行 npm ls -g pnpm,如果列表里有 pnpm,说明安装成功,只是位置不在 PATH 里。第三,npm 全局安装路径是什么。运行 npm config get prefix,在 Windows 上往往得到 C:\Users\你的用户名\AppData\Roaming\npm,你需要去系统环境变量里确认这个路径存在。第四,确认环境变量后,关掉终端重新开一个新终端,再试一次 pnpm -v。这一步千万别省,Windows 的 PATH 修改不会自动刷新到已经打开的终端里,很多人就是卡在这里。
在 macOS 或 Linux 上情况类似,只是路径通常是 /usr/local/bin 或基于 nvm 的 ~/.nvm/versions/node/当前版本/bin。你可以用 which pnpm 来定位可执行文件,返回 not found 就说明 PATH 里没有 node 的全局 bin 目录。
2.2 官方提供的几种安装方式怎么选
pnpm 官方文档里列出了多种安装方式,但第一次接触很容易看花眼。我的建议是:日常个人开发,直接 npm install -g pnpm 是最省事的,前提是你已经装了 Node;公司项目或团队规范化,用 corepack;如果不想套娃,用官方 PowerShell 独立脚本;追求环境统一,用 mise。
用 npm 安装不用多解释,前提是你的 npm 全局目录已经配好 PATH。这里有个“先有鸡还是先有蛋”的小尴尬:你为了装 pnpm 必须先有 npm,装了 npm 又默认带着 npx,所以很多人问“能不能不用 npm 装 pnpm”,当然可以:
powershell复制# Windows PowerShell 官方独立脚本
iwr https://get.pnpm.io/install.ps1 -useb | iex
bash复制# macOS / Linux 官方安装脚本
curl -fsSL https://get.pnpm.io/install.sh | sh -
这种方式会把 pnpm 安装到用户目录下的独立位置,比如 Windows 是 %LOCALAPPDATA%\pnpm,Linux/macOS 是 ~/.local/share/pnpm。好处是不经过 npm 全局环境,坏处是你同样要把这个目录加进 PATH,否则照样报“不是内部或外部命令”。
corepack 是 Node 自带的工具,Node 16.13 之后默认包含。执行 corepack enable 后,系统会启用 pnpm 和 yarn 的自动加载能力。如果你项目 package.json 里写了 packageManager: "pnpm@9.15.0",corepack 会自动使用对应版本,这正是团队场景最需要的可复现特性。不过 corepack 在部分 Linux 发行版上被单独拆成了包,如果提示找不到 corepack,先安装一下系统的 corepack 包。
mise 是一个比较新的工具版本管理器,很多前端开发者把它当成 asdf 的现代替代品。它的核心概念是用一个 .mise.toml 声明文件统一管理 Node、Python、pnpm 等工具的版本。比如写了 pnpm = "10.0.0" 后,mise 会在你进入项目目录时自动调整 PATH。这种方式适合重度依赖多语言工具链的开发者,但如果你只处理前端项目,没必要为了 pnpm 专门引入 mise。
2.3 环境变量与 PowerShell 执行策略问题
Windows 下还有一个非常隐蔽的坑:PowerShell 的执行策略(ExecutionPolicy)。有时候你把 pnpm 装好了、PATH 也配好了,执行 pnpm -v 还是报“无法加载文件,因为在此系统上禁止运行脚本”。这不是 PATH 问题,而是 PowerShell 默认禁止执行脚本。
解决办法是用管理员权限运行 PowerShell,执行:
powershell复制Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
这样只对当前用户生效,允许运行本地脚本,而远程下载的脚本必须有可信签名。这个设置是安全的,不用为了 pnpm 把策略改成 Unrestricted。
另外,如果你用的是 nvm-windows 这种 Node 版本管理器,每次切换 Node 版本后都要注意 npm 全局包的路径跟随。nvm-windows 的 symlink 方案有时候会把全局包目录指到旧版本目录,导致 pnpm 命令时好时坏。这类问题最典型的特征就是:切换 Node 版本后,pnpm -v 突然失败,重新安装 pnpm 又能用了。这种场景要检查的仍然只有一个地方:当前 PATH 里指向的 Node 版本目录,它的全局 node_modules 里有没有 pnpm。
2.4 安装成功后先做三件事验证
装完 pnpm 别急着走,先做三个验证。第一是 pnpm -v,确认版本号正常输出。第二是 pnpm config get store-dir,看 store 路径是否符合预期,这一步能帮你避免后面“不知道磁盘被什么吃掉了”的困惑。第三是 pnpm root -g,确认全局包目录存在。
如果这三条都正常,你的 pnpm 基础环境就算搭好了。下一步就是配置国内镜像,把下载体验提上来。
3. 镜像加速与 install 阶段的实战问题
安装只是第一步,真正让新手抓狂的是 pnpm install 的阶段——要么下载失败,要么安装完跑不了,要么卡在某个依赖上迟迟不动。这一节我会把镜像配置和 install 流程里最常踩的坑串起来讲。
3.1 国内镜像配置:.npmrc 到底改哪里
pnpm 走的是 npm registry 协议,所以镜像配置和 npm 是同一套机制,核心就是 .npmrc 文件。它的优先级从高到低大致是:项目目录 .npmrc > 用户目录 ~/.npmrc > 全局 pnpm config 配置。实际操作中我建议把 registry 写在用户目录的 ~/.npmrc 里,因为这样对所有项目生效,又不会污染项目仓库。
ini复制# ~/.npmrc
registry=https://registry.npmmirror.com
设置完用 pnpm config get registry 验证输出是否为 https://registry.npmmirror.com/。这里有个细节:npmmirror 镜像源在国内基本是最靠谱的选择,它同步 npm 官方仓库的频率已经很高,日常使用出现的版本缺失概率很低。如果你遇到某个包在镜像源上没有,可以临时在项目 .npmrc 里改成官方源,或者直接指定单个包的 registry。
除了 registry,还有几个 pnpm 相关的镜像配置值得留意。比如 @scope:registry 可以给某个私有 scope 单独指定源,公司内部私有仓建议用这个方式,而不是改全局源;publish-registry 只影响发布不影响安装;proxy、https-proxy 则是在公司网络环境下可能需要配置的。
3.2 pnpm install 下载失败与卡顿的排查链路
下载失败这件事,报错信息千奇百怪,但底层原因就那几类。最常见的三种是:DNS 解析失败(ENOTFOUND)、连接超时(ETIMEDOUT、ECONNRESET)、以及证书/代理问题。我自己的排查顺序是固定的:先看报错属于哪一类,再逐层往下查。
第一步是开启详细日志重跑:
bash复制pnpm install --verbose
如果报错发生在某个具体的包下载阶段,日志里通常会直接打出 URL,把这个 URL 复制到浏览器里访问,能打开说明网络通,不能打开要么是镜像问题要么是网络问题。第二步是尝试更换 registry 后再装一次,这是最快验证镜像源是否靠谱的方法。第三步是在公司网络环境或挂了代理的电脑上,检查代理设置是否被 npm/pnpm 继承,必要时在 .npmrc 里显式配置:
ini复制proxy=http://127.0.0.1:7890
https-proxy=http://127.0.0.1:7890
noproxy=localhost,127.0.0.1,.local
卡顿问题另说。如果你发现 pnpm install 卡在某个进度上迟迟不动,先别急着 Ctrl+C。看它卡的位置:卡在“Resolving”阶段,多是指定 registry 响应慢或 DNS 解析慢;卡在“Downloading”某个包,多是该包体积大或网络带宽有限;卡在“Running”脚本阶段,则不是网络问题,是 postinstall 脚本在等待或挂起。
日常体验上,给 pnpm 的下载并发和重试参数做一些调整是很有用的。我一般会在用户级 .npmrc 里加这样一组配置:
ini复制network-concurrency=16
fetch-retries=5
fetch-retry-maxtimeout=120000
network-concurrency 控制同时下载的并发数,默认值在某些弱网络环境下反而容易把连接打满,调低一点更稳;fetch-retries 和 fetch-retry-maxtimeout 控制失败后的重试,适合网络不太稳定的环境。注意这套参数不是越多越好,并发开太高可能触发镜像源限流。
3.3 构建脚本审批:pnpm 10 的 approve-builds 机制
热词里有一条很显眼:run "pnpm approve-builds" to pick which dependencies should be allowed to run。这是 pnpm 10 引入的依赖构建审批机制。
很多包装完之后需要在本地执行构建脚本,比如 esbuild、@swc/core、better-sqlite3、sharp、canvas 这些原生模块,它们往往通过 postinstall 或 install 脚本来下载二进制或编译源码。npm 从来没管过这件事,默认全执行。但 pnpm 出于供应链安全的考虑,从 v10 开始默认阻止依赖包执行构建脚本,你必须显式批准。
于是就会出现一种诡异的现象:pnpm install 成功,但在导入某个库时报错“找不到 bindings”“was compiled against a different Node.js version”等。原因是后安装脚本没跑。
解决方式有两种。最稳妥的是运行:
bash复制pnpm approve-builds
这个命令是交互式的,会列出项目里所有声明了构建脚本但被拦截的依赖,你按空格选择允许哪些,回车确认,pnpm 会自动把选择结果写入 package.json 的 pnpm.onlyBuiltDependencies 字段。另一种方式是自己编辑 package.json:
json复制{
"pnpm": {
"onlyBuiltDependencies": ["esbuild", "sharp", "better-sqlite3"]
}
}
如果你是本地开发,不想一个个选,可以临时关掉这个限制:
bash复制pnpm config set dangerouslyAllowAllBuilds true
注意配置名里的 “dangerously” 不是吓唬人的,它会信任所有依赖的构建脚本,和 npm 的行为一致。公司项目里我不建议全局开这个开关,但在自己本地开发机上用一下,能省很多折腾时间。
3.4 install 成功后 run 失败的三类典型原因
装完了、跑起来却报错,这类问题发生在 pnpm install 之后,但根子往往在 install 那一步。我排查了几十次这类问题后,把它们归纳成三类。
第一类是原生模块没构建好。报错信息通常包含二进制文件的路径缺失,或者 ERR_DLOPEN_FAILED。解决方案就是上一节说的 pnpm approve-builds,然后把相关包重新构建一次,可以用 pnpm rebuild esbuild。第二类是依赖树不一致。项目里有 pnpm-lock.yaml,但你和同事的 lockfile 不同步,或者你改了 package.json 没有更新 lockfile,导致实际安装的依赖跟声明不一致。解决方案是执行 pnpm install --frozen-lockfile(CI 环境)或 pnpm install 重新对齐。第三类是 Node 版本不匹配。有些包的 engines 要求 Node 版本范围,你的 Node 版本太老或太新都会导致运行时报错,我建议项目里用 .nvmrc 配合 nvm 或 mise,把 Node 版本锁到和 CI 一致的版本。
4. 开发与编译过程中的高频场景复盘
“install 成功了,但是启动开发服务器报错”,这类问题几乎每天都会在不同的技术群里看到。比如热词里提到的 deepseek harness 卡在 pnpm dsh web、deerflow 本地 pnpm 开发编译,这些都属于这个场景。这一节我复盘几个典型的开发阶段问题,说说背后的原因和应对方法。
4.1 本地开发编译卡住或超时的常见根因
本地开发编译卡住,主要有三个方向。第一个是构建脚本被拦截。项目里某个依赖需要在 install 阶段执行 postinstall 来生成一些文件,但 pnpm 10 默认拦掉了,结果 dev server 启动时发现缺文件,卡在某个环节干脆不往下走。这种情况的表现往往是:终端里最后一行停在一个 “esbuild” 或者 “node-gyp” 相关的日志上,久久没有输出。处理办法还是回到 approve-builds。
第二个方向是包安装时的网络请求挂起。比如 pnpm dev 启动时按需下载某些二进制文件,下载请求没有超时限制,或者重试次数太少,就会一直挂着。这时可以在 .npmrc 里调大超时和重试参数,也可以手动把依赖预下载好。第三个方向是实际计算量过大。前端项目用 Vite 或 Webpack 编译大型工程时,第一次冷启动本来就慢,很多人误以为卡住了其实是在编译。这种情况我会先用 pnpm exec vite --debug 查看当前到底在做什么,再判断是不是真的有问题。
4.2 等待时长、并发与本地缓存策略的取舍
如果你经常在弱网环境下开发,或公司内网访问公共源很慢,那么“延长等待时间”本身就是一个合理需求,但用对方式很重要。
pnpm 提供了一组 retry 和 timeout 相关的配置,我会在用户级 .npmrc 里这样设置:
ini复制fetch-retries=5
fetch-retry-factor=2
fetch-retry-mintimeout=10000
fetch-retry-maxtimeout=120000
network-concurrency=8
这里 fetch-retry-factor 是重试间隔的指数增长因子,默认 2 表示每次重试的等待时间是上一次的两倍。如果你经常遇到某个大包下载到一半断掉,把 fetch-retries 调到 5 通常能解决。但如果你把 retries 和 timeout 调得太大,一个连不通的源反而会浪费更长时间,这时候要看重试日志里失败的具体原因,不要盲目调参。
本地缓存这块,pnpm install --prefer-offline 会优先使用存在 store 里的缓存副本,--offline 则完全走本地缓存并拒绝网络访问。对稳定复现的 CI 场景来说,用 --frozen-lockfile --offline 配合持久化缓存目录,能大幅缩短构建时间。
4.3 “cli still installed. remove via npm/pnpm if desired.” 是什么情况
这句提示在搜索热词里也出现了。它通常出现在你安装新版本 pnpm 或切换包管理器的时候,代表系统里存在多个来源安装的 pnpm CLI 副本,比如用 npm 全局装了一份,又用 corepack 激活了一份。pnpm 检测到“非当前方案管理的副本还在”,就会在安装结束时给出这句提示。
它不是错误,而是提醒。你可以忽略,也可以清理。我的建议是:统一用同一种方式管理 pnpm,不要把 npm 全局安装和 corepack 混用。要确认当前 pnpm 从哪来,在终端执行 where pnpm(Windows)或 which -a pnpm(Linux/macOS),会列出所有 pnpm 可执行文件的路径。如果同时存在多个,保留你想用的那个,删掉其他来源即可。
具体删除方式:如果是 npm 装的,npm rm -g pnpm;如果是 corepack 激活的,corepack uninstall pnpm。执行完再跑 pnpm -v,确认版本来自你预期的那个管理器。
5. 删除与迁移:把 pnpm 环境彻底清理干净的实操
和安装相对的一个冷门但大量需求的方向是“删除 pnpm”。可能是项目统一回退到 npm,也可能是 pnpm 安装损坏需要彻底重装,更常见的是你想把老的全局 pnpm 换成新版本,但没删干净导致命令错乱。这一节把删除和清理的每一步都讲清楚。
5.1 按安装方式逐一对症卸载
不同方式安装的 pnpm,卸载方法不一样。如果你用 npm 全局安装的,命令很简单:
bash复制npm rm -g pnpm
如果你用 corepack 激活的,先执行:
bash复制corepack uninstall pnpm
如果你用官方脚本装的,需要删除它实际安装到的目录。Windows 下是 %LOCALAPPDATA%\pnpm,Linux/macOS 是 ~/.local/share/pnpm。可以手动删除整个目录,同时检查 ~/.local/bin 或 %LOCALAPPDATA%\Microsoft\WindowsApps 里是否存在 pnpm 相关 shim,一起删除。
如果你用 mise 管的,则在项目里移除 .mise.toml 中的 pnpm 声明,然后执行:
bash复制mise uninstall pnpm
5.2 清理 store 缓存与全局残留
很多人卸载完 pnpm 后发现磁盘空间并没有明显变化,这是因为 pnpm 的全局 store 还躺在那里。在卸载 pnpm 之前,先确认自己的 store 路径:
bash复制pnpm store path
如果 pnpm 还能运行,可以执行 pnpm store prune 来清理未被引用的孤包文件。如果你想彻底删除 store,直接删除对应目录即可。Windows 默认路径一般是 %LOCALAPPDATA%\pnpm-cache,macOS 是 ~/Library/Caches/pnpm,Linux 是 ~/.local/share/pnpm/store。
还要注意检查全局虚拟目录里是否有残留。pnpm 的全局包默认安装在 store 里的一个虚拟目录,光删 pnpm 主程序不会自动清理这些。执行 pnpm root -g 可以看到全局 node_modules 的位置,确认是否需要一并清理。
5.3 从 pnpm 迁移回 npm/yarn 的注意点
如果你是因为项目规范或者团队原因要从 pnpm 切回 npm,直接删掉 node_modules 然后 npm install 通常可以工作,但有几个容易忽略的坑。
首先,pnpm 生成的依赖树结构跟 npm 完全不同。你直接 npm install 会按 package.json 重新解析依赖,lockfile 也是新生成的。这一步本质上是重新安装整棵依赖树,耗时更长属于正常现象。其次,之前因为 pnpm 严格隔离而“被迫显式声明”的幽灵依赖,切回 npm 之后不会被强制校验,但这不代表可以放松依赖声明的规范性,因为旧的幽灵依赖问题会重新冒头。第三,如果项目里用了 workspace,pnpm 的 pnpm-workspace.yaml 和 npm 的 npm-workspaces 字段不是同一套配置,需要同步改造。
6. 真正拉高效率的 pnpm 进阶用法与 store 维护心得
前五节覆盖了基础使用和常见问题排查,这一节聊一些我实际项目里真正用上并受益的进阶玩法,以及 store 的日常维护经验。这些内容不是官方文档里最醒目的部分,但非常实用。
6.1 workspace 协议带来的 Monorepo 体验
pnpm 对 Monorepo 的支持是内置的,只需要一个 pnpm-workspace.yaml 文件。比如我维护的一个前端仓库里,packages 目录下有 ui、utils、app 三个子包,配置文件写:
yaml复制packages:
- packages/*
然后子包之间使用 workspace:* 协议互相引用:
json复制{
"name": "@myorg/app",
"dependencies": {
"@myorg/ui": "workspace:*"
}
}
pnpm 会自动识别 workspace:* 为本地软链。发布时 pnpm 也会自动把 workspace:* 转换成实际的版本号,省去手动替换的麻烦。相比 npm 的 workspaces,pnpm 的 workspace 在依赖隔离和安装速度上优势更明显,子包之间即使引用了同一个依赖的不同版本,也能共存而不冲突。
6.2 pnpm dlx 与 pnpm exec 的正确使用姿势
pnpm dlx 和 pnpm exec 这两个命令经常被混淆。我自己的理解很直白:pnpm dlx 是临时下载并执行某个包,适合一次性工具,例如 pnpm dlx shadcn@latest init 这种场景,它会把包下载到一个临时目录执行完就销毁,不会污染项目依赖。pnpm exec 是在当前项目的 node_modules 里执行命令,类似 npx 的另一种等价形式。
如果你在跑 pnpm dlx 时经常卡在下载阶段,同样可以配置 registry 镜像。这些命令本质上还是会走 registry 拉包,所以前面讲的镜像配置对它同样生效。
6.3 store 的体检、垃圾回收与磁盘占用自查
pnpm 用多久之后,store 会变成一个非常占空间的目录。正常情况下多个项目共享一份文件,空间占用是合理的,但如果经历过频繁换版本、不同项目的 Node 版本差异大,store 里也会沉淀不少孤包文件。我会定期做这几件事。
第一,查看 store 状态:
bash复制pnpm store status
这个命令会检查 store 中的文件是否与项目链接保持一致,如果输出异常,说明 store 或 node_modules 出现了损坏,可能需要删除项目 node_modules 重新 install。
第二,执行垃圾回收:
bash复制pnpm store prune
它会删除没有被任何项目引用的孤包。批量清理的效果往往很可观,我有一次在 CI 上发现缓存目录涨到 40 GB,prune 之后降到 20 GB。
第三,如果磁盘空间告急,但项目又必须保留,可以考虑把 store 迁移到其他盘或网络存储。方法是在项目根目录 .npmrc 或全局 .npmrc 中设置:
ini复制store-dir=D:/pnpm-store
然后重新执行一次 pnpm install,pnpm 会把文件链接关系重建到新 store。注意迁移后旧 store 目录需要手动删除,否则白折腾。
关于 pnpm 的日常维护,我个人养成的习惯是:每次升级 pnpm 大版本前,先看 release notes 里有没有关于依赖构建策略的变更,再跑一个干净项目的 install 做验证;每个项目都把 pnpm.approve-builds 相关的配置沉淀在 package.json 里,方便同事 clone 后一条 install 命令直接跑通;遇到玄学报错先检查是不是缓存和 store 的问题,再考虑重新安装。
如果你正准备从 npm 切到 pnpm,或者正在跟各种 pnpm 报错作斗争,希望这一篇能帮你少走点弯路。如果你还有什么“报错信息很诡异但没搜到解法”的场景,欢迎在评论区把完整报错贴出来,我会尽力帮你定位方向。
