
大型 Node.js 项目在每次都保留相同包的各自副本时,会消耗惊人的磁盘空间。pnpm 通过将包文件存储在共享的内容寻址存储中,并将它们链接到每个项目来解决这一问题。它使用 npm registry,并与相同的 package.json 文件协同工作,同时提供更严格的依赖处理和内置的工作区支持。
本指南讲解如何在 Linux、macOS 和 Windows 上安装 pnpm,然后用它创建项目、安装 Node.js 运行时、添加和更新包、运行脚本、从 npm 迁移,以及在 monorepo 中协作。
语法
该命令的一般形式如下:
pnpm [options]
大多数 pnpm 命令都有直接的 npm 等价命令。例如,pnpm install 安装项目依赖,pnpm add 记录一个新依赖,pnpm run 执行 package.json 中的脚本。
pnpm 如何存储包
pnpm 将包文件保存在你机器上的共享存储中。项目链接到这些文件,而不是把相同的包内容复制到每个 node_modules 目录。如果十个项目使用同一包版本,pnpm 只存储一次它的文件。
生成的 node_modules 布局还能防止包导入它们未声明的依赖。这种更严格的行为有助于暴露缺失的依赖声明,而 npm 扁平的依赖布局有时会隐藏这些。
你仍然要把 package.json 和 pnpm-lock.yaml 锁文件提交到版本控制。共享存储和生成的 node_modules 目录不应提交。
安装 pnpm
pnpm 12 是一个原生可执行文件。安装后它在没有 Node.js 的情况下也能运行,因此你可以在还没有 Node.js 的机器上设置 pnpm,之后再添加运行时。npm 方式是个例外,它需要 Node.js 22.13 或更新版本来运行安装程序。
安装独立版本
独立安装程序会下载适用于你平台的 pnpm 二进制文件,不需要 Node.js 存在。在 Linux、macOS 和其他 POSIX 系统上,运行:
curl -fsSL https://get.pnpm.io/install.sh
|
sh -
如果未安装 curl,wget 以相同方式工作:
wget -qO- https://get.pnpm.io/install.sh
|
sh -
要安装特定发布版本,为脚本设置 PNPM_VERSION 变量:
curl -fsSL https://get.pnpm.io/install.sh
|
env
PNPM_VERSION
=
12.3.4 sh -
在 Windows PowerShell 上,使用:
Invoke-WebRequest
https
:
//
get
.
pnpm
.
io
/
install
.
ps1
-UseBasicParsing
|
Invoke-Expression
Microsoft Defender 有时会阻止以这种方式安装的 pnpm 可执行文件。在 Windows 上,下面用 npm 的方式是更安全的选择。
用 npm 安装
如果你已有 Node.js 22.13 或更新版本,运行 npm 安装程序:
npx get-pnpm
运行安装程序需要 Node.js,但之后运行 pnpm 不需要。如果你的 Node.js 版本较旧,请参阅我们在 Ubuntu 26.04 上安装 Node.js 和 npm 的指南,或者让 pnpm 按下一节所述为你安装运行时。
更新已有安装
从 pnpm v11.10.0 起,你可以就地迁移到当前发布版本:
pnpm self-update
安装程序完成后打开一个新终端,然后验证命令:
pnpm --version
12.3.4
为项目固定 pnpm 版本
在 package.json 中记录 pnpm 版本,可以让贡献者和 CI 任务保持在同一个发布版本上。创建项目时添加该字段:
pnpm init --init-package-manager
这会写入一个带有确切 pnpm 版本的 packageManager 条目。在一个已有该条目的项目中,pnpm self-update 会更新该条目而不动你的全局安装,并且 pnpm 在下次命令时切换到记录的发布版本。
Corepack 也可以固定包管理器版本,并且它仍然有效。它在 pnpm 的位置安装一个 JavaScript 填充(shim),因此每次 pnpm 调用都会先启动 Node.js 再运行 pnpm 本身。pnpm 文档现在建议直接安装 pnpm。
用 pnpm 安装 Node.js
pnpm 可以自己安装并管理 Node.js 运行时。这在通过独立脚本获得 pnpm 且完全没有 Node.js 的机器上很方便。
安装当前 LTS 发布版本并用 -g 把它放到你的 PATH 上:
pnpm runtime
set
node lts -g
你也可以指定一个主版本或要求最新发布版本:
pnpm runtime
set
node
22
-g
pnpm runtime
set
node latest -g
用通常的方式检查结果:
node --version
v24.21.0
同一命令也能安装其他运行时,例如 pnpm runtime set deno 2 -g 或 pnpm runtime set bun latest -g。
初始化项目
创建一个目录并初始化 package.json 文件:
mkdir example-app
cd
example-app
pnpm init
pnpm 写入一个带默认字段的 package.json 且不询问任何问题。当你也希望项目被固定到你正在运行的 pnpm 版本时,添加 --init-package-manager。如果你已有一个用 npm 创建的项目,pnpm 可以不经任何转换就使用其现有的 package.json。
安装包
用 pnpm add 添加运行时依赖:
pnpm add express
pnpm 安装该包,把它记录到 package.json 的 dependencies 下,并创建或更新 pnpm-lock.yaml。
用 -D(--save-dev 的简写)添加开发工具:
pnpm add -D eslint
要安装特定包版本,在 @ 后追加它:
pnpm add express@5.1.0
当你克隆一个已有的 pnpm 项目时,用以下命令安装其包文件中声明的所有内容:
pnpm install
对于 CI 或其他可复现环境,防止锁文件被更改:
pnpm install --frozen-lockfile
当 package.json 和 pnpm-lock.yaml 不一致时,该命令会失败,而不是解析出新的依赖树。一旦 pnpm 检测到 CI 环境,它会自动开启此模式,因此该标志主要在你希望本地也有相同行为时才有意义。
安装全局包
用 -g 全局安装一个命令行工具:
pnpm add -g http-server
如果 pnpm 报告找不到全局二进制目录,运行:
pnpm setup
打开一个新终端让更新后的 PATH 生效,然后重复全局安装。把应用依赖保持在每个项目的本地,全局安装只保留跨项目需要的命令。
更新与检查包
检查哪些依赖有可用更新:
pnpm outdated
在 package.json 的版本范围内更新依赖:
pnpm update
要将一个包更新到其最新发布的版本并更改其声明的范围,运行:
pnpm update express --latest
用以下命令列出当前项目的直接依赖:
pnpm list --depth
0
当你需要理解某个包为何存在时,使用:
pnpm why express
移除包
用以下命令把依赖从 node_modules、package.json 和锁文件中移除:
pnpm remove express
对于全局安装的包,添加 -g:
pnpm remove -g http-server
运行脚本与包二进制
pnpm 像 npm 一样读取 package.json 的 scripts 段。给定这个文件:
{
"scripts"
:
{
"start"
:
"node index.js"
,
"lint"
:
"eslint ."
}
}
用以下命令运行具名脚本:
pnpm run lint
当脚本名与 pnpm 命令不冲突时,可以省略 run:
pnpm start
用 pnpm exec 运行当前项目中安装的二进制:
pnpm
exec
eslint .
对于你不想作为依赖添加的临时包,使用 pnpm dlx:
pnpm dlx create-vite@latest my-app
pnpm exec 运行已有的本地依赖,而 pnpm dlx 临时获取一个包并运行其命令。
从 npm 迁移项目
从包含 package-lock.json 的项目生成 pnpm 锁文件:
pnpm import
该命令读取 npm 锁文件并创建 pnpm-lock.yaml。接下来安装导入的依赖树:
pnpm install
在移除 package-lock.json 之前运行项目的测试和构建。提交 pnpm-lock.yaml,并将 CI 命令从 npm ci 更新为 pnpm install --frozen-lockfile。
如果仓库在其 packageManager 字段中记录了包管理器,用以下命令更新该固定:
pnpm self-update
使用 pnpm 工作区
pnpm 工作区把一个仓库中的多个包组合起来。在仓库根目录的 pnpm-workspace.yaml 中定义其包位置:
packages
:
-
"apps/*"
-
"packages/*"
从根目录为每个工作区包安装依赖:
pnpm install
递归地对定义了该脚本的所有包运行脚本:
pnpm --recursive run build
当命令应针对单个包时,使用 --filter:
pnpm --filter web-app add zod
pnpm --filter web-app run test
过滤器可以按名称、目录、依赖关系或更改的文件选择包,这使它们在较大的 monorepo 和 CI 任务中很有用。
快速参考
如需可打印的快速参考,请参阅 pnpm 速查表。
| Task | Command |
|---|---|
| 检查 pnpm 版本 | pnpm --version |
| 更新 pnpm 自身 | pnpm self-update |
| 安装 Node.js 运行时 | pnpm runtime set node lts -g |
| 初始化项目 | pnpm init |
| 初始化并固定 pnpm 版本 | pnpm init --init-package-manager |
| 安装项目依赖 | pnpm install |
| 用未更改的锁文件安装 | pnpm install --frozen-lockfile |
| 添加依赖 | pnpm add |
| 添加开发依赖 | pnpm add -D |
| 添加全局包 | pnpm add -g |
| 显示过时包 | pnpm outdated |
| 更新包 | pnpm update |
| 将一个包更新到最新版 | pnpm update |
| 移除包 | pnpm remove |
| 运行脚本 | pnpm run |
| 运行本地包二进制 | pnpm exec |
| 临时运行包 | pnpm dlx |
| 列出直接依赖 | pnpm list --depth 0 |
| 解释已安装的包 | pnpm why |
| 导入 npm 锁文件 | pnpm import |
| 在所有工作区运行命令 | pnpm --recursive |
| 针对单个工作区包 | pnpm --filter |
故障排查
pnpm: command not found after installation(安装后 pnpm: command not found)——打开一个新终端,让 shell 重新加载安装程序所做的 PATH 更改。如果 pnpm 仍不可用,检查你的 shell 配置中是否有 PNPM_HOME 条目,并重新加载相应的文件,例如 source ~/.bashrc 或 source ~/.zshrc。如果条目缺失,重新运行独立安装程序。
ERR_PNPM_UNSUPPORTED_ENGINE——项目中的某个包需要你没有的 Node.js 版本。用 pnpm runtime set node
A package import worked with npm but fails with pnpm(npm 导入成功但 pnpm 失败)——代码可能依赖一个未在其自身 package.json 中声明的传递依赖。把缺失的包作为直接依赖添加,而不是依赖 npm 更扁平的 node_modules 布局。
The lockfile was created by a newer version of pnpm(锁文件由更新版本的 pnpm 创建)——自 pnpm 11 起,CI 中的安装会在一个由更新 pnpm 主版本编写的 pnpm-lock.yaml 上失败,而不是悄悄重写它。将 CI 中的 pnpm 版本与你本地使用的版本匹配,最可靠的方式是用 packageManager 字段固定它。
ERR_PNPM_OUTDATED_LOCKFILE in CI(CI 中 ERR_PNPM_OUTDATED_LOCKFILE)——提交的 pnpm-lock.yaml 与 package.json 不匹配。在本地运行 pnpm install,查看并提交更新后的锁文件,然后在 CI 中重新运行 pnpm install --frozen-lockfile。
结论
pnpm 使用熟悉的 npm 包生态系统,同时减少重复的包存储并强制更清晰的依赖声明。如果你在比较工作流,请参阅 npm 命令指南了解等价的 npm 命令和锁文件行为。