用 pnpm 11 省掉项目里的 .nvmrc 与 .npmrc

一个仓库里的「环境约束文件」考古

随便打开一个稍有年头的 Node.js 仓库,根目录大概率有这几个文件:

  • .nvmrc --- 告诉 nvm / fnm / volta 用哪个 Node.js 版本
  • .npmrc --- registry、auth token、pnpm 行为、auto-install-peersshamefully-hoist 这些杂物的栖息地
  • package.jsonengines.node --- 给包管理器装依赖时做兜底校验
  • package.jsonpackageManager --- Corepack 用来固定 pnpm/yarn 版本

四份地方各管一摊,每次升级 Node 要改三处,新人入职得讲三遍「先 nvm use,再 corepack enable,registry 看一眼 .npmrc......」。在 monorepo 里更糟糕:子项目要不要带自己的 .npmrc?根目录的版本要不要往下复制? 一不留神,CI 用 18 跑过,本地 20 又装出另一份 lockfile。

pnpm 11 把这套东西收得相当彻底------只要愿意把 pnpm 升上去,本仓库里的 .nvmrc.npmrc 都可以删掉。这篇笔记记录我在 bolt 仓库迁移过来的过程和踩到的几个点。

pnpm 11 提供了什么

1. packageManager 字段:脱离 Corepack 自己工作

json 复制代码
{
  "packageManager": "pnpm@11.1.0"
}

packageManager 是 Node.js 官方定义的字段,本来是给 Corepack 看的------Corepack 看到这一行,就会自动下载并启用对应版本的 pnpm。

但 Corepack 自己有点麻烦:Homebrew 装的 Node 默认禁用 Corepack,得手动 corepack enable;企业网络下首次激活经常被代理卡住;COREPACK_ENABLE_AUTO_PIN 在某些版本会自动改写 package.json,反而搅乱 git diff。

pnpm 11 给出了一个更优雅的回路:pnpm 自己读 packageManager 字段 ,发现当前运行的版本对不上时,按 pmOnFail 设置来处理。新版默认 pmOnFail: download------直接下载对的版本接管这次调用,不动 package.json,不依赖 Corepack。

旧版那一坨 managePackageManagerVersions / packageManagerStrict / packageManagerStrictVersion 配置全部被 pmOnFail 一个键吃掉了:

旧设置 替代值
managePackageManagerVersions: true pmOnFail: download (默认)
managePackageManagerVersions: false pmOnFail: ignore
packageManagerStrict: false pmOnFail: warn
packageManagerStrictVersion: true pmOnFail: error

pnpm 11 还新增了 devEngines.packageManager支持版本 rangepackageManager 字段只能写精确版本),并把解析后的版本写进 pnpm-lock.yaml:

json 复制代码
{
  "devEngines": {
    "packageManager": {
      "name": "pnpm",
      "version": ">=11.0.0",
      "onFail": "download"
    }
  }
}

两个字段如何取舍?

  • 想跟 npm/yarn 生态共用一套机制 → 用 packageManager,pnpm 11 默认会下载缺失版本,不再需要 Corepack
  • 想配合 lockfile 把版本最终固化、且接受版本范围 → 用 devEngines.packageManager

2. engines.runtime / devEngines.runtime:pnpm 来管 Node、Bun、Deno

这是真正干掉 .nvmrc 的关键。pnpm 10.14+ 引入了 devEngines.runtime,10.21+ 又补了 engines.runtime

json 复制代码
{
  "engines": {
    "runtime": {
      "name": "bun",
      "version": "1.3.13",
      "onFail": "download"
    }
  }
}

两者的区别:

  • engines.runtime --- 发布约束。声明此包对外发布时依赖哪个运行时(CLI 包尤其重要,pnpm 会把可执行文件绑死到指定 Node 版本)。
  • devEngines.runtime --- 项目约束 。声明本地开发用的运行时,pnpm install 时自动按版本下载到 pnpm-managed runtime store,写进 lockfile。

pnpm install 一跑,正确版本的 Node/Bun/Deno 就到位了,路径由 pnpm 注入到当前 shell。等价于 nvm use + 「保证装过这个版本」,而且不需要每个开发者本地都装 nvm。

CLI 触发同一行为的命令是 pnpm runtime set node 24.4.0(也支持 pnpm runtime set bun ...)------它会写入 devEngines.runtime 并把版本固定下来。

顺带一提:旧的 pnpm env use 已被 pnpm runtime set 替代,两者效果相同,前者保留为别名。如果你之前用 useNodeVersion 配置,pnpm 11 已经移除,必须迁到 devEngines.runtime

3. .npmrc 被「削权」:非 auth 配置一律走 YAML

pnpm 11 的破坏性变更里最显眼的一条:.npmrc 只保留 auth / registry 两类配置 ,其余 pnpm 行为必须搬家到 pnpm-workspace.yaml(monorepo)或全局 ~/.config/pnpm/config.yaml

典型迁移:

yaml 复制代码
# pnpm-workspace.yaml
registries:
  default: https://registry.npmmirror.com/
  "@my-org": https://private.example.com/

allowBuilds:
  bun: true
  electron: true
  core-js: false

packageConfigs:
  "apps/web":
    saveExact: true
  "packages/sdk":
    savePrefix: "~"

minimumReleaseAge: 1440        # 新发布的包静默期,11 起默认值
strictDepBuilds: true

注意几个细节:

  • package.json 里的 pnpm 字段也不再被读取了,所以以前写在那里的 onlyBuiltDependencies、overrides 都得迁出来。
  • 网络相关项(httpProxystrictSsl 等)目前仍兼容从 .npmrc 读取,但官方建议挪到 config.yaml
  • 环境变量从 NPM_CONFIG_* 改成 PNPM_CONFIG_*,CI 脚本里那一长串 NPM_CONFIG_REGISTRY=... 要顺手换掉。

本仓库的最终形态

bolt 仓库现在的根 package.json 大致长这样:

json 复制代码
{
  "name": "bolt",
  "private": true,
  "packageManager": "pnpm@11.1.0",
  "engines": {
    "runtime": {
      "name": "bun",
      "version": "1.3.13",
      "onFail": "download"
    }
  },
  "devDependencies": {
    "@typescript/native-preview": "7.0.0-dev.20260414.1"
  }
}
  • .nvmrc 不存在 :本仓库实际跑在 Bun 上,engines.runtime 已经把 Bun 版本钉死,pnpm install 时按需下载。
  • .npmrc 不存在 :本仓库没有私有 registry 也没有镜像需求;如果要换 npm 镜像,再单写一个 .npmrc 就够了,毕竟它现在只剩 registry/auth 一个职责。
  • packageManager 锁 pnpm :任何人 clone 下来跑 pnpm install,pnpm 自己会校对版本并按需下载。
  • 配合 5 月 8 日那则笔记 :Bun 不是装在 node_modules/.bin 里的 devDependency,而是 pnpm runtime;调用直接 bun ...,PATH 由 pnpm 接管。

四个文件压缩成了一个 package.json 的几个字段。

什么时候还需要 .npmrc

.npmrc 没死透,下面这些场景仍然要留:

  1. 私有 registry / scope 映射 ------虽然 pnpm 11 提供了 registries 配置,但 npm 自身、CI 缓存层、其它工具链仍读 .npmrc,多写一份能省麻烦。
  2. 企业镜像或代理 token ------auth token 一般会塞 .npmrc~/.config/pnpm/auth.ini
  3. 被其它工具读取 ------某些独立工具(npm publish 直接、IDE 插件、Snyk/Renovate 等)仍按 npm 规范读 .npmrc

我的判断:只要不出现 auth token、私有 registry,就不必为了 pnpm 行为单独建 .npmrc

给团队的迁移清单

  1. 把 CI 和开发环境的 Node 升到 22+(pnpm 11 硬性要求)。
  2. 在根 package.json"packageManager": "pnpm@11.x.y",删掉 .nvmrc
  3. pnpm runtime set node <version>(或 bun/deno)填好 devEngines.runtime,必要时同步 engines.runtime
  4. pnpm-v10-to-v11 codemod 自动迁 .npmrc 里的非 auth 配置到 pnpm-workspace.yaml
  5. package.json 里的 pnpm 字段配置(onlyBuiltDependencies / overrides 等)挪到 pnpm-workspace.yaml,注意 onlyBuiltDependencies 已被 allowBuilds map 取代。
  6. CI 把 nvm use 这类命令换成 corepack enable && pnpm install(或直接 pnpm install,pnpm 11 自己会处理版本)。
  7. 第一次 pnpm install 后检查 pnpm-lock.yaml 是否记录了 runtime 版本和 packageManagerDependencies,确认锁定生效。
  8. .nvmrc,做一次完整冷启动验证(先 rm -rf node_modulespnpm install 再启动应用)。

小结

  • packageManager + devEngines.packageManager 接手「锁包管理器版本」,pnpm 11 自己负责下载,不再需要 Corepack。
  • engines.runtime / devEngines.runtime 接手「锁 Node / Bun / Deno 版本」,pnpm install 时自动下载,.nvmrc 可以删。
  • .npmrc 被砍到只剩 registry/auth;pnpm 行为搬到 pnpm-workspace.yaml
  • 本仓库实测下来,根目录不需要 .nvmrc 也不需要 .npmrc,所有约束都集中在 package.json 一处。

升级 pnpm 11 的本质收益不只是性能,更是把「四份散文件维护的项目环境」收敛回一个 package.json 单点定义------一次配置,团队、CI、未来的自己都受益。

相关推荐
猪猪聪明_V40 分钟前
前端码农的本地项目启动器
前端·javascript
时光不负努力1 小时前
每天一个高级前端知识 - Day 21
前端
暗不需求1 小时前
前端性能优化 防抖与节流完全指南:从原理到最佳实践
前端·javascript·面试
@大迁世界1 小时前
45.什么是内联条件表达式(inline conditional expressions)?在事件处理里怎么用?
开发语言·前端·javascript·react.js·ecmascript
一颗趴菜1 小时前
微信小程序如何去下载PDF呢
前端·javascript
KaMeidebaby2 小时前
卡梅德生物技术快报|细菌 FISH 实验 + 流式细胞术:尿路感染活菌快速定量系统实现与数据验证
前端·数据库·其他·百度·新浪微博
昆曲之源_娄江河畔2 小时前
DBGridEh Footer的使用
前端·数据库·delphi·dbgrideh
廖松洋(Alina)2 小时前
02数据模型与单词仓库-鸿蒙PC端Electron开发
前端·华为·electron·开源·harmonyos·鸿蒙
幽络源小助理2 小时前
最新短网址系统源码 分用户链接 - 幽络源免费源码分享
前端·php