pnpm 在国内网络下的三个坑:ECONNRESET、allowBuilds 与废弃的 pnpm 字段

今天搭这个博客时,pnpm create astro 卡了整整 12 分钟最后失败。排查过程踩了三个坑,每一个单看都很反直觉,记录下来。

坑一:单连接测速快,不代表并发下载也快

一开始我用 curl 测速,淘宝镜像源跑出 253 KB/s,比官方源的 108 KB/s 快一倍多,于是果断切了过去。

结果 pnpm 一跑就满屏报错:

GET https://registry.npmmirror.com/... error (ECONNRESET)
GET https://registry.npmmirror.com/... error (ETIMEDOUT)
[WARN] Tarball download average speed 14 KiB/s ... is below 50 KiB/s

问题在于:curl 测的是单个连接下 1 个文件的下载速度,而 pnpm 会同时发起几十个并发请求。 这两件事对网络的要求完全不同。

ECONNRESET 的意思是连接被中途掐断,不是速度慢。说明这条网络对高并发连接有干扰(或 CDN 对高频请求做了限流)。单连接测速根本测不出这个问题。

修法是降低并发 + 提高重试:

pnpm config set network-concurrency 3
pnpm config set fetch-retries 6
pnpm config set fetch-retry-mintimeout 5000
pnpm config set fetch-retry-maxtimeout 120000
pnpm config set fetch-timeout 600000

改完立刻顺畅,287 个包一路装完,零个 ECONNRESET。

教训:诊断网络问题要看并发场景,别只看单文件下载速度。

坑二:pnpm 默认不让依赖跑构建脚本

装完包,新的错误来了:

[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.28.2

这是 pnpm 10+ 引入的安全策略——默认禁止依赖执行 postinstall 脚本,防止供应链攻击。但 esbuild 恰恰需要 postinstall 来就位它的二进制文件。

更麻烦的是,这个“忽略”会让 pnpm install 退出码为 1。而 pnpm 在执行任何脚本(包括 pnpm build)前会先做一次依赖状态检查:

at runDepsStatusCheck (pnpm.mjs:273323)
at runPnpmCli (pnpm.mjs:271523)

这个检查内部调用 pnpm install,一失败,整个 pnpm build 就被连坐阻断,根本跑不到 astro。这是我卡最久的地方——报错信息看起来是“构建失败”,其实构建压根没开始。

坑三:pnpm 11 换掉了配置字段,而且是两次

我先按老经验,在 package.json 里加:

{
  "pnpm": {
    "onlyBuiltDependencies": ["esbuild"]
  }
}

pnpm 回了一句:

The “pnpm” field in package.json is no longer read by pnpm.

package.json 里的 pnpm 字段在 pnpm 11 已被彻底废弃,设置搬去了 pnpm-workspace.yaml。

换到 pnpm-workspace.yaml 写 onlyBuiltDependencies,还是不生效。最后发现 pnpm 自己把配置文件改写成了这样:

allowBuilds:
  esbuild: set this to true or false
onlyBuiltDependencies:
  - esbuild

那个 set this to true or false 不是乱码,是 pnpm 生成的占位提示——它在告诉我该填什么。填上 true 之后,postinstall 立刻执行:

.../esbuild@0.28.2/node_modules/esbuild postinstall$ node install.js
.../esbuild@0.28.2/node_modules/esbuild postinstall: Done

所以 pnpm 11 的正确写法是 allowBuilds:

# pnpm-workspace.yaml
allowBuilds:
  esbuild: true

最终可用的完整配置

# 网络:治 ECONNRESET
pnpm config set network-concurrency 3
pnpm config set fetch-retries 6
pnpm config set fetch-retry-mintimeout 5000
pnpm config set fetch-retry-maxtimeout 120000
# pnpm-workspace.yaml:治被拦截的构建脚本
allowBuilds:
  esbuild: true
# .npmrc:用国内镜像(单连接更快)
registry=https://registry.npmmirror.com

小结

现象 真实原因 修法
满屏 ECONNRESET 高并发连接被干扰,不是速度问题 降并发 + 增重试
pnpm build 直接退出 依赖状态检查失败连坐了构建 放行构建脚本
配置改了不生效 pnpm 11 废弃了旧字段 改用 allowBuilds

一句话总结:遇到“构建失败”时,先确认构建到底有没有真的开始跑。

← 返回文章列表