ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

pnpm store迁移实战:从C盘换个位置,一举根治磁盘空间焦虑

pnpm store迁移实战:从C盘换个位置,一举根治磁盘空间焦虑 我上个月磁盘空间又红了C盘还剩不到5个GB。一开始以为是Windows Update留的垃圾清完还是那个鬼样子。直到我把磁盘分析工具打开才发现罪魁祸首是 pnpm 的 store 目录——整整三十多个GB的包缓存全堆在系统盘里。那一刻我意识到很多人对 pnpm 的存储位置一知半解只知道它“省空间”却不知道它把空间省到了哪里、又该怎样把它的存储位置彻底统一到自己想要的磁盘上。这篇文章就是一次完整复盘把 pnpm store 的迁移、配置、报错修复以及团队侧的规范全部梳理一遍帮你把磁盘空间焦虑从根上解决。1. 磁盘空间焦虑的根源pnpm 的 store 到底藏在哪里、占了多少空间1.1 理解 storepnpm 为什么自带磁盘空间焦虑pnpm 和 npm、yarn 最大的区别就是它引入了一个全局的、基于内容寻址的存储目录官方叫 store。每一个从 registry 下载的包都会解压到 store 里项目的 node_modules 其实不做物理复制而是通过硬链接指向 store 里的文件。这样一来如果一百个项目都用同一个版本的 lodash磁盘上只会存在一份 lodash 文件其余全部是零开销的链接安装速度和磁盘占用都非常理想。但问题也出在这里这个中央 store 默认放在当前用户的主目录下面而用户主目录通常都在系统盘上。对于一个常年只给 C 盘分了 120GB 的开发者来说pnpm 用得越久store 膨胀得越厉害系统盘就越容易被吃满。很多人以为 store 只是“缓存”删了也不心疼。其实 store 里的包不只是缓存它是 pnpm 整个依赖体系的内容本体。项目 node_modules 里的硬链接文件本质上是 store 中文件的一个目录项。如果 store 目录里的原始目录项被删掉只要项目目录里还有硬链接文件内容就不会消失空间也不会被释放。这一点后面迁移时特别重要。用一个生活化的类比store 相当于一个中央仓库node_modules 里的文件相当于仓库货架上货物的一串标签。标签可以到处贴但货物本体只放在仓库里。如果你的仓库恰好安置在系统盘那系统盘自然越来越挤。1.2 不同平台上默认存储路径与查询方式不同操作系统上的默认 store 路径不太一样系统默认路径WindowsC:\Users\用户名\AppData\Local\pnpm\storemacOS~/Library/pnpm/storeLinux~/.local/share/pnpm/store注意实际路径可能会带上版本子目录比如 Windows 上常见的是C:\Users\用户名\AppData\Local\pnpm\store\v3。不要靠记忆猜直接执行下面的命令看真实位置pnpm store path这条命令返回的才是 pnpm 当前真正使用的 store 路径。想快速看目录占多大Windows 下可以用WizTree或TreeSize扫一遍Linux/macOS 下用du -sh ~/.local/share/pnpm/store或du -sh ~/Library/pnpm/store。如果你的系统盘已经告急先跑一下这条命令大概率会看到一个让你倒吸一口凉气的数字。接下来要做的就是把这个目录整体搬到其他盘。2. 统一存储位置的核心心法store-dir 与项目盘符必须同区2.1 硬链接的跨卷限制为什么只改 store 路径还不够很多人的第一步操作是执行pnpm config set store-dir D:\pnpm-store执行完之后满心期待结果 D 盘缓存是过去了C 盘空间却一点没少。原因在于硬链接有一个非常硬核的限制硬链接不能跨文件系统Windows 上就是不能跨盘符Linux/macOS 上就是不能跨 mount 点。pnpm 安装依赖时会做一个判断如果 store 和项目在同一个卷内就用硬链接省空间、速度快如果不在同一个卷pnpm 会老老实实地把文件复制到项目的 node_modules 里。也就是说store 在 D 盘但你的项目还在 C 盘那每个项目的 node_modules 依然会在 C 盘生成一份完整拷贝C 盘空间照样被大量占用只是包的“原始副本”换到了 D 盘而已磁盘焦虑一点没缓解。所以“统一存储位置”的真正含义不是单纯把 store 换到一个大硬盘而是让 store 和你的项目落在同一个分区里。比如我现在的个人习惯是所有项目统一放在D:\Projectsstore 统一放在D:\pnpm-store这样 pnpm 在安装时就能用硬链接把 node_modules 里的内容全部指向 D 盘 storeC 盘几乎不会因为 node_modules 增长而消耗空间。如果你的开发环境特殊项目必须放在 C 盘那最合理的配置其实是把 store 也留在 C 盘。虽然 C 盘会被占用但至少不会出现“一份包复制两份”的浪费。请记住这句话store 放在大硬盘只是第一步项目跟着 store 走才叫真正的统一。2.2 同一个盘上硬链接才有效如何确认是否生效判断 pnpm 到底有没有用硬链接方法很直接。在 Linux/macOS 上先找一个项目里已安装的包文件用 stat 查看硬链接数stat -c %h node_modules/.pnpm/lodash4.17.21/node_modules/lodash/index.js如果输出大于 1说明确实有多个目录项指向同一个文件硬链接生效。在 Windows PowerShell 里可以使用fsutil hardlink list node_modules\.pnpm\lodash4.17.21\node_modules\lodash\index.js如果返回了多个路径甚至能看到 store 目录里的路径就说明硬链接已经建立。如果硬链接数只有 1那基本可以断定 pnpm 走了复制策略。还有一个更简单的间接判断方法在两个项目里安装同一个依赖版本然后去 store 对应的包目录里看文件大小或链接数。如果同一份文件被多个项目引用链接数明显增加说明硬链接是生效的。这个检查在迁移完 store 之后特别有用能第一时间发现问题。3. 一步一步把 pnpm store 从 C 盘搬到 D 盘3.1 先给当前 store 做瘦身pnpm store prune 和 status迁移之前强烈建议先清理一波历史残留。pnpm 的 store 里会保存那些已经不被任何项目引用的“孤儿包”比如你升级依赖后旧版本的小版本残留又比如你删除了某个项目但 store 里没来得及清理的缓存。这些孤儿包除了占空间没有任何用处。安全清理命令是pnpm store prune这个命令会把当前 store 中“不再被任何项目引用”的包删除且不会影响现有项目的运行。如果不放心可以先执行pnpm store status检查 store 的内部状态是否健康。通常输出一些检查结果如果没有任何异常再执行 prune。这一步虽然不能把 store 清零但能清理出几个 GB 到几十个 GB 都有可能取决于你废弃过多少项目。我的一个老项目根目录被删了但 store 里残留了接近 6GB 的旧依赖一次 prune 直接回到清爽状态。3.2 配置新路径三种方式任选配置 store 路径有三种方式适用场景不同。方式一全局配置对当前用户的所有项目生效pnpm config set store-dir D:\pnpm-store这个命令会往用户级.npmrc文件里写入store-dirD:\pnpm-store。Windows 上就是C:\Users\用户名\.npmrc。方式二项目级配置在项目根目录创建或修改.npmrcstore-dirD:\pnpm-store这种方式适合团队想统一某个仓库的存储路径但前面说过绝对路径在团队里容易引发跨平台问题需要谨慎。方式三临时指定只对当前这一次安装生效pnpm install --store-dir D:\pnpm-store适合你在 CI 脚本或者临时测试时使用。配置完成之后立即验证一下pnpm store path如果输出变成了D:\pnpm-store\v3之类的路径说明配置已经生效。注意目录不存在时 pnpm 会在首次写入时自动创建但我建议你还是手动建好目录并确保当前用户对这个目录有完整的读写权限。我之前在 Windows 上遇到过权限问题pnpm 写 store 时报错后来发现是目录权限被限制导致。3.3 让现有项目重建 node_modules 指向新 store配置新路径之后旧项目里的 node_modules 并不会自动知道 store 已经搬家了。你需要让项目重新“链接”到新 store。这里有两种方案。方案一保留旧 store 缓存复制到新位置减少重新下载。先获取旧 store 路径和当前配置后的新 store 路径pnpm store path假设旧路径输出是C:\Users\you\AppData\Local\pnpm\store\v3新路径输出是D:\pnpm-store\v3。那么你需要把旧路径v3目录里的内容复制到新路径D:\pnpm-store\v3下面。Windows 下可以用 robocopyrobocopy C:\Users\you\AppData\Local\pnpm\store\v3 D:\pnpm-store\v3 /E /COPYALLLinux/macOS 下用 rsyncrsync -av ~/Library/pnpm/store/v3/ /D/pnpm-store/v3/复制完成后在需要保留的旧项目目录里执行pnpm install --force--force会强制重新构建 node_modules 的链接关系让它指向新的 store。因为新 store 里已经有缓存这一过程不需要重新从网络下载速度非常快。方案二不复制旧缓存直接重新下载。如果旧 store 里残留太多垃圾或者你已经执行过 prune直接设置为新路径后在每个项目里重新执行pnpm install即可。缺点是所有依赖要重新下载一遍依赖多了会比较慢但换来的是一尘不染的全新 store。关键点来了所有需要保留的项目必须全部重新 install 完成之后才能删除旧 store。否则旧项目 node_modules 里的硬链接还指向旧 store 的文件 inode此时删掉旧 store 目录并不会释放空间因为那些文件仍然被项目里的硬链接占用着。正确的顺序永远是先让项目链接到新 store再删旧 store。4. 新环境安装 pnpm 时顺手解决 不是内部或外部命令 这一串问题4.1 安装 pnpm 的几种方式与各自的 bin 路径新机器上配置 pnpm经常被一串报错气到上头pnpm 不是内部或外部命令也不是可运行的程序或批处理文件。这个问题的本质很简单——pnpm 装上了但它所在的 bin 目录没有加到系统的 PATH 环境变量里。先说常见的安装方式。方式一通过 npm 全局安装npm install -g pnpm此时 pnpm 会被安装到 npm 的全局 bin 目录。Windows 默认一般是C:\Users\用户名\AppData\Roaming\npmLinux 一般是/usr/local/bin或者~/node_modules/.bin。方式二通过 corepack 启用Node.js 自带corepack enable corepack prepare pnpmlatest --activate这个方式依赖 Node 自带的 corepackbin 路径通常和 Node 安装目录挂钩比如C:\Program Files\nodejs\下会生成 pnpm 的可执行入口。方式三Windows 独立脚本安装iwr https://get.pnpm.io/install.ps1 -useb | iex这种方式会把 pnpm 默认安装到用户目录下的pnpm文件夹比如C:\Users\用户名\pnpm如果你设置了环境变量PNPM_HOME则安装到PNPM_HOME指定的目录。不论哪种方式只要命令行敲pnpm没反应第一件事就是检查 PATH。4.2 排查 PATH 和 npm prefix让 pnpm 可被直接调用排查步骤非常固定npm ls -g pnpm看 pnpm 是否确实装上了。然后npm prefix -g这条命令会输出 npm 全局安装的根目录。比如输出C:\Users\you\AppData\Roaming\npm那你需要确认这个目录在 PATH 中。接着用where pnpmWindows或which pnpmLinux/macOS确认命令是否能被找到。如果没有输出就说明 PATH 里缺少 bin 目录。解决方案把 npm prefix 指向的目录加入环境变量Path。Windows 上按Win R输入sysdm.cpl在“高级-环境变量”里编辑Path新建一条填入 npm 全局目录Linux/macOS 在~/.bashrc或~/.zshrc里 export PATH。如果你不满 C 盘想连 pnpm 本身都安装到 D 盘可以直接改 npm 的全局 prefixnpm config set prefix D:\npm-global npm install -g pnpm然后把D:\npm-global加入 PATH。这样全局安装的 pnpm 和相关命令行工具都统一落到 D 盘系统盘又少一块压力。关于 PATH还有一个隐藏雷点如果你同时用 npm 全局包方式和 corepack 方式安装了 pnpm两份 pnpm 的 bin 目录都在 PATH 里很容易出现版本不一致或者命令指向错乱的问题。建议只保留一种安装方式其他方式先卸载干净。4.3 顺便解决 pnpm 下载失败和镜像配置问题热搜词里还有一个高频问题pnpm 下载失败。原因绝大多数是网络环境导致的 registry 访问不稳定。最直接的应对是配置国内镜像源。在用户级.npmrc文件里写入registryhttps://registry.npmmirror.com store-dirD:\pnpm-store这样既把 registry 切到了国内镜像又顺带统一了 store 路径一举两得。如果你的项目里有 electron、puppeteer 这类需要下载二进制文件的依赖光配 registry 还不够它们有自己的下载源需要单独配镜像。以 electron 为例electron_mirrorhttps://npmmirror.com/mirrors/electron/ electron_builder_binaries_mirrorhttps://npmmirror.com/mirrors/electron-builder-binaries/配好之后再遇到下载失败先执行pnpm cache delete清掉 pnpm 的 HTTP 缓存再重新试试安装。注意这里的pnpm cache是下载缓存和 store 是不同的概念。下载缓存负责加速网络请求store 负责包文件实体存储两者不要搞混。如果项目已经因为多次下载失败残留了半成品可以在项目目录删除node_modules和pnpm-lock.yaml之外不要乱删 lock 文件先执行pnpm install问题不大。真正顽固的失败往往和网络代理、防火墙有关这个就另说了至少镜像方案能解决九成问题。5. 进阶玩法团队项目里如何统一存储位置让每个开发机都清爽5.1 项目级 .npmrc 到底该不该提交进仓库有人在团队仓库里直接写死store-dirD:\pnpm-store然后提交代码结果同事在 Mac 上拉下来pnpm 直接抱怨找不到这个路径。所以项目级.npmrc里写绝对路径是一种非常差的做法。更合理的方案是在仓库的.npmrc里只放和项目相关的配置比如 registry、镜像地址、hoist 规则等不要放绝对路径。store-dir 属于每个开发机的本地偏好应该放到用户级.npmrc里或者通过环境变量在每台机器上单独设置。如果团队确实需要强制所有成员的 store 路径一致比如内部有一个共享文档规范那也是通过“环境变量 安装脚本”的方式统一配置而不应该硬编码在项目仓库里。CI 场景就更不应该用项目级绝对路径临时参数或环境变量最合适。5.2 CI 中缓存 pnpm store团队级的磁盘空间优化CI 机器虽然是临时的但每次跑构建都重新下载几十 MB 乃至几百 MB 依赖既慢又浪费流量本质上也是一种“时间焦虑 带宽焦虑”。GitHub Actions 里比较标准的做法是先把 pnpm 的 store 目录找出来然后把它塞进缓存。- uses: pnpm/action-setupv2 with: version: 8 run_install: false - name: Get pnpm store directory id: pnpm-store run: echo STORE_PATH$(pnpm store path) $GITHUB_OUTPUT - name: Setup pnpm store cache uses: actions/cachev3 with: path: ${{ steps.pnpm-store.outputs.STORE_PATH }} key: pnpm-store-${{ hashFiles(pnpm-lock.yaml) }} restore-keys: | pnpm-store-这里关键点在于先用pnpm store path拿到当前 runner 上的 store 路径再把它作为缓存目录。pnpm-lock.yaml不变的时候缓存命中率很高安装依赖的速度会非常快。GitLab CI 里思路一样把pnpm store path指到的目录打包成产物或者挂到 runner 的缓存目录里。实际使用中我建议不要在 CI 里把 store 和项目目录分到不同卷因为 CI 的缓存机制本身已经限制了硬链接跨卷的问题直接用默认路径反而最稳。另外要特别提醒不要试图把 pnpm store 放到 NFS 或 SMB 网络共享盘上。硬链接和文件锁在网络文件系统下表现非常不稳定容易出现各种诡异问题。pnpm 的 store 设计本质上是面向本机磁盘的团队共享依赖应该通过私有 npm 源来解决而不是共享 store 目录。6. 迁移和统一之后怎么验证真正生效及如何清理历史垃圾6.1 验证新 store 已生效的四个检查点迁移完成后不要急着收工花两分钟验证一下第一执行pnpm store path确认输出已经是新路径。第二在项目里执行pnpm install确认没有任何报错node_modules 正常生成。第三用之前提到的stat -c %h或者fsutil hardlink list检查一个包文件的硬链接数。如果链接数大于 1说明确实和新 store 建立了硬链接关系而不是复制。第四打开磁盘分析工具看看 C 盘空闲空间是否增加、D 盘空间是否被 store 占用。如果 C 盘没有任何变化多半是旧项目还挂着旧 store 的硬链接或者项目本身还在 C 盘、pnpm 走的复制策略。如果检查发现仍然走复制优先确认项目路径和 store 路径是否在同一个挂载卷。Windows 下还要检查目标盘的文件系统FAT32、exFAT 不支持硬链接必须用 NTFS。查看文件系统类型可以用fsutil fsinfo volumeinfo D:6.2 安全清理旧 store 与历史项目把空间真正拿回来很多人在迁移之后直接删旧 store却发现空间一点没少然后跑来问“为什么删了没反应”。其实原因在前面已经提到过旧项目的 node_modules 里还存着指向旧 store 文件的硬链接文件没有被真正删除空间自然不释放。安全的操作顺序应该是对所有需要保留的项目执行pnpm install --force让 node_modules 全部重新指向新 store。运行pnpm store prune清理新 store 里的孤儿包。确认旧 store 路径不再被任何项目引用后再删除旧 store 目录。如果你已经彻底不再用 pnpm 管理某些旧项目最干脆的办法是直接删除整个项目目录然后删除旧 store空间会一次性全部释放。我遇到过最深的坑就是第一次迁移时没有重装旧项目只把 C 盘原 store 目录删了结果空间纹丝不动。后来才想明白硬链接的内容只有当所有链接都被删除时才会真正释放。那次之后我就养成了习惯迁移 store 之后第一时间把老项目全部pnpm install --force一遍再回头清理旧目录。6.3 我踩过一次最深的坑迁移后忘了重装旧项目C盘空间一个没少上面那件事值得展开多说两句。当时我图省事用 robocopy 把 store 复制到 D 盘改了 store-dir然后满怀信心地删除 C 盘 store 目录。结果删完之后 C 盘可用空间连 1GB 都没多出来。我当时第一反应是“系统出 bug 了”后来读了一堆文档才意识到是硬链接机制在作祟。Windows 上如果对一个文件建立了多个硬链接文件数据的生命周期由所有目录项共同决定。删除 store 里的原始目录项只是让文件少了一个“标签”但项目 node_modules 里的“标签”还在文件内容依然存续。只有当所有硬链接都被删除磁盘空间才会真正释放。这件事给我的教训是一切与 store 相关的迁移都必须以“项目 node_modules 是否重新链接”为判断依据而不是“store 目录是否删除”。这也是为什么我不推荐大家直接复制 store 然后删除旧目录除非你已经非常清楚自己每个项目的状态。现在我的做法很简单所有项目统一放在 D 盘store-dir 固定为D:\pnpm-store每个月定时跑一次pnpm store pruneC 盘再也没出现过因为 pnpm 导致的容量告急。如果你也有 C 盘焦虑不妨按这个思路操作一遍把 pnpm 的存储位置彻底“圈养”到大硬盘里省下的空间远比想象中多。
返回列表