Back

你应该从 npm 切换到 pnpm 吗?

你应该从 npm 切换到 pnpm 吗?

你可能在 monorepo 配置、CI 配置以及框架文档中看到过 pnpm 的身影。也许有同事对它推崇备至。但对于你的日常前端工作来说,切换是否真的值得这份折腾,还是说它解决的只是你尚未遇到的问题?

以下是你真正需要了解的内容。

核心要点

  • pnpm 使用基于内容寻址的存储,结合硬链接和符号链接,消除了幻影依赖问题,并减少了多项目下的磁盘占用。
  • pnpm 11 延续了 pnpm 10 中引入的生命周期脚本限制,需要通过 pnpm approve-builds 显式授权,以降低供应链风险。
  • 工作区过滤功能以及 workspace:* 协议使 pnpm 非常适合使用 Turborepo 或 Nx 的 monorepo。
  • 通过 package.json 中的 packageManager 字段锁定包管理器,并在 CI 中使用 --frozen-lockfile 实现确定性安装。
  • pnpm 在 monorepo 和多项目场景中表现尤为出色;对于单包应用,继续使用 npm 通常也无妨。

pnpm 与 npm 的本质区别

pnpm 不仅仅是更快的 npm,真正的差异在于结构层面。

npm 创建的是扁平化的 node_modules 目录,这意味着你的代码可能会意外引入你从未声明为依赖的包——这就是所谓的幻影依赖问题。pnpm 则采用基于内容寻址的存储,结合硬链接和符号链接,因此只有你显式声明的依赖才能在根级别访问。这使得依赖解析更加严格,也更具可复现性。

另一个主要差异是磁盘占用。pnpm 全局只存储每个包版本一次,并通过硬链接将其链入你的项目。如果你在本地维护多个 Next.jsVite 项目,就不会在每个项目中重复占用数百兆的空间。

如需深入了解其存储模型和依赖隔离机制,值得浏览一下官方的 pnpm 与 npm 对比文档

pnpm 11 与安全优先的转向

pnpm 11 于 2026 年 4 月发布,延续了近几个主要版本中逐渐形成的趋势:将安全性和确定性置于纯粹速度之上。

在切换之前最需要了解的关键行为是:pnpm 现在默认会阻止依赖项的生命周期脚本执行,除非显式授权。 这就是 pnpm 10 中引入的 pnpm approve-builds 工作流。如果你安装了一个带 postinstall 脚本的包——这在需要编译原生二进制的包(如 sharpesbuildcanvas)中很常见——安装会成功完成,但该脚本要等到你授权后才会运行。

这会让那些期望一切开箱即用的开发者感到意外。你可以交互式地运行 pnpm approve-builds,或在 pnpm-workspace.yaml 中配置允许构建的依赖项:

onlyBuiltDependencies:
  - sharp
  - esbuild

这是一种有意的权衡:前期多些摩擦,后期少些供应链意外。

工作区与 Monorepo 工具链

这正是 pnpm 明显领先的地方。npm 工作区也能用,但在更大规模的项目中,pnpm 的工作区实现更加符合工程实践。

你在 pnpm-workspace.yaml 文件中定义包:

packages:
  - 'apps/*'
  - 'packages/*'

然后即可开箱即用地享受强大的过滤功能:

pnpm --filter @myapp/ui build
pnpm --filter "...^@myapp/ui" test  # 在依赖 ui 的包中运行测试

对于使用 TurborepoNx 的团队,pnpm 的工作区协议(workspace:*)集成顺畅,能让内部依赖关系保持显式清晰。

使用 packageManager 字段锁定版本

无论你是否使用 Corepack,有一个实用的做法是:在 package.json 中添加 packageManager 字段,标明项目所期望的包管理器及其版本。

{
  "packageManager": "pnpm@11.0.0"
}

在启用了 Corepack 的环境中可以强制执行该约定,即使未启用 Corepack,它也能向团队和 CI 系统清晰地传达意图。

与 GitHub Actions 集成的 CI 配置

- uses: pnpm/action-setup@v6
  with:
    version: 11
- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: 'pnpm'
- run: pnpm install --frozen-lockfile

在 CI 中始终使用 --frozen-lockfile。它能防止 lockfile 被静默修改,并确保安装的确定性。

官方的 pnpm CI 文档 还提供了适用于 GitHub Actions、GitLab CI、CircleCI、Azure Pipelines 和 Bitbucket Pipelines 的示例。

何时值得切换

如果你在 monorepo 中工作、管理多个本地项目,或希望默认获得更严格的依赖隔离,那么 pnpm 是最合适的选择。pnpm approve-builds 的安全模型对于注重供应链卫生的团队来说确实非常有用。

如果你只是维护一个单包应用,而 npm 用得也挺顺手,那么迁移的摩擦可能并不值得。npm 工作区对于直观的项目结构已经足够成熟,作为生态系统默认值仍然具有实实在在的价值。

结语

坦率地说:先在新项目中尝试 pnpm。其命令几乎完全一致,lockfile 可读性强,而且大多数前端框架——包括 Next.js、Vite 和 Astro——都无需额外配置即可支持。如果它的严格性和磁盘节省契合你的工作流,再扩展到现有项目就是一个轻松得多的决定。

常见问题

通常可以。删除 node_modules 和 package-lock.json,然后运行 pnpm import 转换你的 lockfile,接着执行 pnpm install。要留意你的代码可能在 npm 扁平结构下依赖的幻影依赖——pnpm 会以缺失导入的形式暴露出来,你只需将它们显式添加到 package.json 即可修复。

approve-builds 是一个允许运行生命周期脚本的包白名单。你可以完全关闭这道关卡,但这样做会重新引入 pnpm 11 旨在缓解的供应链风险。推荐的做法是仅授权那些你信任且确实需要 postinstall 步骤的包,例如原生二进制编译器。

绝大多数包无需改动即可使用。问题主要出现在那些假定扁平 node_modules 结构或依赖幻影依赖的包上。大多数流行库早已修复了这些问题。如果有兼容性问题,可在 .npmrc 中通过 public-hoist-pattern 设置为特定包模拟 npm 风格的提升,作为应急手段。

对于冷安装,得益于并行解析和内容寻址存储,pnpm 通常更快。对于多个共享依赖的项目的热安装,差距会非常明显,因为 pnpm 通过硬链接复用已下载的包。在带有完整缓存的 CI 环境中差距会缩小,但 pnpm 通常仍保持一定优势。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay