pnpx、pnpm dlx、pnpm exec 的关系:到底怎么选?

在前端工程里,我们经常需要「执行一个 CLI」:用脚手架建项目、跑代码生成器、临时调用某个工具,或者在项目里执行已经装好的依赖命令。
pnpm 里和这件事相关的命令有三个:pnpm dlxpnpxpnpm exec。它们长得像,但职责并不一样,混用是最常见的踩坑点。

先说结论:

  • pnpx 就是 pnpm dlx 的别名,两者完全等价,不用纠结。
  • pnpm dlx / pnpx:从 registry 临时下载并运行一个包(不装为项目依赖),相当于 pnpm 版的 npx
  • pnpm exec:在当前项目里执行已经安装好的依赖命令,不会下载任何东西。

一句话区分:要下载着跑用 dlx,跑本地已装好的用 exec

一、三者分别是什么

1) pnpm dlx

pnpm 官方子命令。从 registry 拉取一个包,不将其安装为项目依赖,热加载后运行它暴露的默认命令。

pnpm dlx create-vue my-app

特点:

  • 跑完即走,不会污染当前项目的 package.jsonnode_modules
  • 拉取过的包仍会进入 pnpm 的全局 store 缓存,下次执行更快。
  • 可显式指定版本:pnpm dlx create-vue@next my-app
  • 支持 --package 一次安装多个包再执行:pnpm --package=yo --package=generator-webapp dlx yo webapp
  • 支持 --allow-build(v10.2.0+)放行 postinstall 脚本。
  • 支持 catalog: 协议,复用 workspace 中定义的版本。

2) pnpx

官方文档原话:Aliases: pnpx is an alias for pnpm dlx

也就是说 pnpx foopnpm dlx foo 行为完全一致,只是少打几个字。从 pnpm v7 起官方在命名上对齐了 npm exec / npx 的风格,文档与团队规范里更推荐写成 pnpm dlxpnpx 留作快捷写法。

⚠️ 别把 pnpx 当成 npx 的同义替换。两者目的类似(都是临时执行 CLI),但 pnpx 走的是 pnpm 的依赖解析与 store 缓存,安装速度、存储占用、排错思路都和 npm 那套不同。

3) pnpm exec

当前项目作用域内执行 shell 命令:把项目的 node_modules/.bin 加入 PATH,从而可以直接调用项目依赖里的可执行文件。

# 项目里已经装了 jest,不必再全局安装
pnpm exec jest

特点:

  • 只跑本地已安装的命令,不下载新包。如果依赖里没有对应二进制,会直接报错。
  • exec 关键字在不与 pnpm 内置命令冲突时可省略pnpm jest 等价于 pnpm exec jest
  • 天然支持 workspace:pnpm -r exec rm -rf node_modules 可在每个子包里递归执行。
  • 支持 --shell-mode, -c--parallel--filter 等选项。

二、核心区别对比

维度pnpm dlxpnpxpnpm exec
身份官方子命令pnpm dlx 的别名官方子命令
是否下载包是,临时从 registry 拉取是,同 dlx,只跑本地已装的
是否写入项目依赖否(本来就在依赖里)
执行范围临时隔离环境临时隔离环境当前项目(node_modules/.bin
典型语义「我没装,帮我拉来跑一次」dlx「我已经装了,在项目里跑」
是否支持 workspace 递归是(-r
推荐场景文档 / CI / 脚本本地手敲快捷运行项目内的工具链

三、一张图理清决策

要执行一个 CLI

    ├── 这个包在当前项目依赖里已经装好了吗?
    │       ├── 是  ──▶ pnpm exec <cmd>   (或省略 exec:pnpm <cmd>)
    │       └── 否  ──▶ 需要临时下载着跑
    │                       ├── 写文档 / CI / 脚本 ──▶ pnpm dlx <pkg>
    │                       └── 本地手敲求快   ──▶ pnpx <pkg>   (等价)

    └── 如果是「创建新项目」类脚手架 ──▶ 也可用 pnpm create <flavor>
            (pnpm create vite  ≡  pnpm dlx create-vite)

四、典型使用场景

1) 脚手架初始化(新项目)

# 推荐:写在文档里语义最清晰
pnpm dlx create-vite@latest my-app

# 等价快捷写法
pnpx create-vite@latest my-app

# 专门用于 create-xxx 的语法糖
pnpm create vite my-app

2) 临时执行一次性工具

# 临时跑一下代码生成器 / 格式化器,不污染项目依赖
pnpm dlx prettier --write .
pnpm dlx cowsay "hello"

3) 运行项目内已安装的工具

# 项目里已装了 eslint / jest / prisma,直接用 exec
pnpm exec eslint src
pnpm exec jest
pnpm exec prisma generate

# 等价(不与内置命令冲突时可省略 exec)
pnpm eslint src

注意 prisma generate 这类命令:如果 prisma 已经是项目依赖,用 pnpm exec prisma generate 更准确(不会重复去 registry 拉包);如果临时在没有装它的环境里跑,才用 pnpm dlx prisma generate

4) 团队脚本与 CI

# CI 日志里意图清晰,便于排查
pnpm dlx @graphql-codegen/cli --config codegen.ts
pnpm -r exec rimraf node_modules

五、常见问题与误区

1) pnpxpnpm dlx 到底有啥区别?

没有区别。pnpxpnpm dlx 的别名,行为完全一致。选哪个只关乎可读性和团队规范——文档和脚本里写 pnpm dlx,本地手敲图省事用 pnpx

2) 为什么同一命令在不同机器上表现不一致?

常见原因:

  • Node 与 pnpm 版本不同(如 --allow-build 仅 v10.2.0+ 支持)。
  • 未显式锁版本,CLI 默认取最新,导致行为漂移。
  • 网络源(registry)配置不同,拉取结果或速度有差异。

建议:关键 CLI 显式指定版本,例如 create-vite@latest 或固定到具体版本号。

3) pnpm exec 找不到命令怎么办?

说明该命令对应的依赖没有在当前项目安装exec 不会自动下载,要么先 pnpm add -D <pkg>,要么改用 pnpm dlx <pkg> 临时跑。

4) 可以照搬 npm 的 npx 经验吗?

部分可以,但不建议无脑套用。pnpx / pnpm dlx 对标 npx,但走的是 pnpm 的 store 与符号链接机制;而 pnpm exec 更接近 npm exec 的语义(运行本地依赖)。搞清「下载着跑」还是「跑本地的」就不会乱。

5) 这些命令会污染全局环境吗?

不会。它们都不会把工具长期装成全局包。dlx 跑完即走(包进 store 缓存但不挂到项目),exec 只复用项目已有依赖。

六、实用命令清单

# === 临时下载并运行(dlx / pnpx) ===
pnpm dlx create-vite@latest my-app     # 推荐写法
pnpx create-vite@latest my-app         # 等价别名
pnpm create vite my-app                # create-xxx 的语法糖
pnpm dlx prettier --write .            # 临时跑一次性工具
pnpm --package=yo --package=generator-webapp dlx yo webapp   # 多包执行

# === 在项目内运行已安装的命令(exec) ===
pnpm exec eslint src
pnpm exec jest
pnpm exec prisma generate
pnpm eslint src                        # 省略 exec 的等价写法
pnpm -r exec rimraf node_modules       # workspace 递归执行

七、如何选型(可直接照抄)

  • 临时下载运行一个包:pnpm dlx(文档 / CI)或 pnpx(本地手敲)。
  • 创建新项目脚手架:pnpm create <flavor>,或 pnpm dlx create-<flavor>
  • 运行项目里已安装的工具:pnpm exec <cmd>(或省略 exec)。
  • workspace 批量在子包里执行:pnpm -r exec <cmd>
  • 团队规范、CI、README:统一用 pnpm dlxpnpm exec,语义最清晰。

记牢一句话就能应对绝大多数情况:要下载着跑用 dlx,跑本地已装好的用 execpnpx 只是 dlx 的短别名。