[前端/nodejs/js/包管理器] pnpm:基于内容寻址存储与严格依赖隔离的高性能 JavaScript 包管理器
0 序
- 定位:"Fast, disk space efficient package manager"(快速、省磁盘的包管理器) —— 通过"内容寻址存储 + 硬链接 + 符号链接"三件套,实现跨项目依赖去重、严格依赖隔离与确定性安装。
1 概述: pnpm
产品介绍
- 产品定位:pnpm(Performant npm)是一个面向 JavaScript / Node.js 生态的高性能包管理器,是 npm 的社区级替代品。
- 它解决的核心痛点是 npm / Yarn Classic 时代的三大问题:重复占用磁盘、安装速度慢、"幽灵依赖"(phantom dependency,即源码能访问未声明依赖)。
诞生的背景与原因:
- 使用 npm 时,每个项目都会在各自
node_modules里复制一份完整依赖;100 个项目用 lodash,磁盘上就有 100 份 lodash。 - npm/Yarn Classic 采用"扁平化提升(hoisting)"的
node_modules,导致源码可以访问未在package.json中声明的依赖(幽灵依赖),埋下不确定性与安全隐患。 - 为解决上述问题,pnpm 引入内容可寻址存储(Content-Addressable Storage, CAS):所有包只按内容哈希存一份在全局 store 中,项目通过硬链接引用,实现"一次下载、处处复用"。
官方URL:
- 官网: https://pnpm.io
- GitHub: https://github.com/pnpm/pnpm
- npm 包: https://www.npmjs.com/package/pnpm
开源与许可:MIT License;GitHub 创建于 2016 年 1 月;截至 2026 年 8 月约 36.1K Star、1.6K Fork、456 位贡献者、2.6K 开放 Issue,主语言已由 TypeScript 转为 Rust(pnpm 12 重写)。
发展历程
| 时间 | 版本/事件 | 要点 |
|---|---|---|
| 2016-01 | 项目创建 | 由 Zoltan Kochan 发起,登 GitHub |
| 2017 | v1.x | 初代版本,确立 CAS + 符号链接/硬链接思路 |
| 2018 | v2.x / v3.x | 改进锁文件机制(确定性安装);引入 workspaces 工作区,monorepo 体验成型 |
| 2021 前后 | v6.x | 生态成熟期,社区大规模采用(据公开资料整理) |
| 2025 | v10.x | 安全性默认收紧(如 onlyBuiltDependencies / approve-builds 机制)、JS 生态 JSR 支持等 |
| 2026-04-28 | v11.0 | 官方博客:收紧 v10 周期引入的安全默认值;发布流程弃用 npm CLI 兜底、改用原生实现;store 索引由"每包一个 JSON"改为单一 SQLite 数据库;全局安装相互隔离 |
| 2026-08-26 | v12.0 | Rust 重写版本转正(stable):命令、flag、设置与锁文件格式与 v11 完全兼容,属"非迁移式"重写;原生二进制,安装后无需 Node.js 亦可运行 |
说明:v1~v6 的年份细节来自二手技术文章整理,仅作趋势参考;v11/v12 为 pnpm 官方博客确认。
主要功能
- 依赖安装/更新/卸载:
pnpm install、pnpm add、pnpm update、pnpm remove等完整命令集。 - 确定性锁文件:
pnpm-lock.yaml保证不同机器/不同时间安装结果一致(--frozen-lockfile用于 CI)。 - Monorepo / 工作区(workspaces):原生支持多包仓库,优化大量项目共享依赖场景。
- 动态包执行:
pnpm dlx <pkg>等价于 npx,可临时执行包命令而不全局安装。 - Node 运行时管理:
pnpm env use/pnpm runtime可充当 Node.js 版本管理器(nvm 类似能力)。 - 依赖修补:
pnpm patch支持对第三方依赖打补丁。 - 构建脚本安全管控:默认阻止依赖包自动执行 postinstall 等构建脚本,需
pnpm approve-builds显式批准。 - 安全与合规:
pnpm sbom生成 SBOM、pnpm licenses list列出依赖许可证。 - 全局安装隔离:v11 起全局安装相互隔离,不再互相干扰。
核心优势
- 磁盘占用极省:内容寻址存储 + 硬链接,同一版本依赖在全局只存一份,跨项目共享;同一包不同版本只存差异文件。官方口径可"节省 gigabytes 级空间"(社区常引约 70% 的估算)。
- 安装速度快:相比"解析—下载—全部写入"的传统三阶段,pnpm 采用"解析—计算目录结构—链接"三阶段,已缓存依赖直接链接,官网称比同类快最多 2 倍。
- 严格依赖隔离:默认非扁平
node_modules,项目源码只能访问package.json声明过的依赖,从根上消除幽灵依赖。 - 确定性:
pnpm-lock.yaml提供可复现安装。 - Monorepo 一等公民:workspaces 体验成熟,微软在 Rush 仓库中"数百项目、每日数百 PR"生产使用(Rush 团队引语)。
- 跨平台:Windows / Linux / macOS 全支持。
- 生态演进快:pnpm 12 用 Rust 重写,进一步压低安装/解析开销。
主要短板
- 学习/迁移成本:非扁平
node_modules与 npm 心智不同,旧工具链(如部分构建脚本、仅支持扁平结构的工具)需适配;nodeLinker可回退hoisted模式但会失去隔离收益。 - 与"扁平依赖"假设的兼容性:少数不规范依赖(偷偷引用未声明包)在 pnpm 严格模式下会
MODULE_NOT_FOUND,需要补声明或改配置。 - Windows 体验:符号链接/硬链接在 Windows 上权限与性能表现弱于 POSIX;官方文档亦提示 Windows Defender 会拖慢安装、可能拦截独立脚本安装的可执行文件。
- 对 Node 版本有门槛:pnpm 11 经 npm 安装要求 Node.js ≥ 22(较新的 Node 需求)。
- 早期"过快变更":v10→v11 安全默认收紧、v11→v12 重写,升级节奏快,旧教程易过时。
局限性
- 不能替代 Node 本身:它管理依赖与运行时,不提供 JS 运行时(除非用
pnpm runtime装 Node)。 - 非官方标配:需自行安装(npm/脚本/包管理器),与"随 Node 自带"的 npm 相比多一步引导。
- 极端场景无优势:对单项目、依赖极少的场景,隔离收益不明显;某些同时需要"严格隔离 + 扁平物理目录"的工具链需额外配置。
- PnP(零安装)不支持:功能对比表中
零安装一列为 ❌(Yarn PnP 独占)。
适用场景
- 大型项目 / Monorepo(多包仓库,共享依赖多)—— pnpm 的主场。
- CI/CD 环境:快速安装 +
--frozen-lockfile确定性构建。 - 磁盘敏感环境:多项目并存、需要省空间。
- 对依赖安全与整洁有高要求的团队:严格隔离、构建脚本白名单。
- 需要同时管理多套 Node 版本 + 依赖的工程化场景(借助
pnpm env)。
同类竞品(必读)
| 工具 | 定位 | 关键差异 |
|---|---|---|
| npm | Node 官方默认包管理器 | 扁平提升式 node_modules,生态最广、零配置,但磁盘冗余、易产生幽灵依赖;v7+ 也支持 workspaces |
| Yarn (Berry) | Facebook/社区维护的替代品 | 确定性好,v2+ 主打 Plug'n'Play(PnP,零 node_modules);workspaces 成熟 |
| pnpm | 高性能第三方替代品 | 内容寻址存储 + 硬链接 + 严格隔离,磁盘最省、隔离最严格 |
| Bun | 运行时级"全家桶"(bundler/runner/installer) | 以 JavaScript 运行时为基础集成包管理,安装快、但生态较新 |
官方功能对比(摘要,pnpm.io/zh/feature-comparison):pnpm 独占
内容可寻址存储(Yarn 在 nodeLinker=pnpm 时也支持)、副作用缓存、目录、配置依赖项、构建脚本安全性、脚本运行前自动安装等;零安装(PnP)pnpm 不支持;动态包执行(pnpm dlx / yarn dlx / npx)三家都有。
发展趋势
- Star 趋势:GitHub Star 从 2016 年创建至今约 36.1K(2026-08),2025 年 v10 发布后进一步加速;长期呈稳步上升曲线。
- Fork/社区趋势:Fork 约 1.6K,贡献者 456 人,周活活跃(最近一次 release 为 2026-08 当日),Issue 2.6K 但维护健康分 75。
- 技术方向:Rust 重写(pnpm 12) 是标志性拐点——从"Node 脚本工具"进化为"原生二进制包管理器",未来安装/解析性能与脱离 Node 依赖是主旋律。
- 总结:pnpm 正从"更快的 npm"走向"以 Rust 原生化 + 严格隔离 + Monorepo 深度支持为核心的全能型包管理器",是大前端与大型工程化项目的默认选择趋势。
2 工作原理与架构
概念术语
| 术语 | 含义 |
|---|---|
| 内容寻址存储(CAS) | 以文件内容哈希为唯一标识的全局存储;相同内容的文件只存一份,与项目无关 |
| 硬链接(hard link) | 多个目录项指向同一 inode(同一物理文件),不额外占空间;删除任一链接不影响其他 |
| 符号链接(symlink) | 指向路径的引用(可跨文件系统、可指目录);pnpm 用它搭建依赖树 |
| 全局 store | pnpm 统一存放所有已下载包的仓库,默认在用户目录(可用 pnpm store path 查询) |
| 非扁平 node_modules | node_modules 顶层只放直接依赖的符号链接,间接依赖藏在 .pnpm 虚拟 store 中 |
| 幽灵依赖(phantom deps) | 未在 package.json 声明却可被 import 的依赖,源于扁平提升 |
| 工作区(workspaces) | 一个仓库内管理多个包的 monorepo 模式 |
| 锁文件(lockfile) | pnpm-lock.yaml:记录精确解析结果,保证确定性 |
架构与运行原理
核心数据流(图示):
一次 pnpm install 的三阶段:
| 阶段 | 动作 |
|---|---|
| ① 依赖解析 | 识别 store 中缺失的包并拉取到全局 store |
| ② 目录结构计算 | 根据依赖树计算 node_modules 布局 |
| ③ 链接依赖项 | 已缓存包直接从 store 硬链接到 node_modules,无需重新下载 |
运行要点:
- 一次下载、处处复用:所有包文件只存在于全局 store,多个项目共享同一版本 = 共享同一批硬链接,磁盘零冗余。
- 只存差异:同一包的不同版本,仅把发生变化的文件加入 store(例如某包 100 个文件升级只改 1 个,则只新增 1 个文件)。
- 顶层只放直接依赖:
node_modules根目录下的包是符号链接(指向.pnpm/<name>@<version>/node_modules/<name>),源码只能 require 到声明过的依赖,杜绝幽灵依赖。 - 严格、确定性:
pnpm-lock.yaml锁定精确版本与结构;CI 用--frozen-lockfile拒绝漂移。 - 安全默认:依赖的 postinstall 等构建脚本默认不自动执行(
pnpm approve-builds白名单放行),降低供应链风险。 - v12(Rust 重写):安装、解析、链接管线用 Rust 原生实现,整体性能与冷启动进一步优化,且安装后可不依赖 Node 运行。
3 使用指南
安装部署
前置要求:用独立脚本安装或装 v12 原生二进制时无需 Node.js;用 npm 安装 pnpm 11 则要求 Node.js ≥ 22。Node 兼容性(官方表):Node 18/20/22/24/26 均支持 pnpm 10/11/12(Node 16 仅 pnpm 8 支持,Node 14 及以下不支持)。
Windows(必读)
NPM 方式(推荐/必读)
- 推荐(npm 方式):
npm install -g pnpm(官方因 Windows Defender 可能拦截独立脚本而推荐此方式)
亲测 by Git-Bash
$ npm install -g pnpm
added 1 package in 4s
1 package is looking for funding
run `npm fund` for details
//查看二进制程序的路径
$ which pnpm
/d/Program_Files/nodejs/node-v25.9.0-win-x64/pnpm
$ pnpm --version
11.24.0
$ pnpm store path
C:\Users\xxx\AppData\Local\pnpm\store\v11
//修改全局存储路径——核心存储仓库,所有下载的包内容都存放在这里 (可选步骤)
$ pnpm store path
E:\Program-Data\pnpm-data\store\v11
$ pnpm config set store-dir "E:\Program-Data\pnpm-data\store"
默认: C:\Users\xxx\AppData\Local\pnpm\store\v11
//修改全局安装的包路径、可执行命令文件的路径、pnpm运行状态文件的路径、下载缓存(加速后续安装)的路径 (刚安装时,默认值为 undefiend)
pnpm config set global-dir "E:\Program-Data\pnpm-data\global"
pnpm config set global-bin-dir "E:\Program-Data\pnpm-data\bin"
pnpm config set state-dir "E:\Program-Data\pnpm-data\state"
pnpm config set cache-dir "E:\Program-Data\pnpm-data\cache"
-
目录解释(修改后的路径,需要及时手动创建一下)
-
store: 核心存储仓库,所有下载的包内容都存放在这里 -
global: 全局安装的包位置 -
bin: 可执行命令文件 -
state: pnpm运行状态文件 -
cache: 下载缓存,加速后续安装
-
其他安装方式
-
PowerShell 独立脚本:
Invoke-WebRequest https://get.pnpm.io/install.ps1 -UseBasicParsing | Invoke-Expression -
winget install -e --id pnpm.pnpm/scoop install nodejs-lts pnpm/choco install pnpm
提示:可用管理员 PowerShell 将 store 加入 Defender 排除:
Add-MpPreference -ExclusionPath $(pnpm store path)
Linux / macOS
- curl 脚本:
curl -fsSL https://get.pnpm.io/install.sh | sh -(或 wget 版) - npm 方式:
npm install -g pnpm - macOS 可用 Homebrew:
brew install pnpm - 常见报错
error while loading shared libraries: libatomic.so.1→ 安装libatomic1(Debian/Ubuntu)或libatomic(Fedora/RHEL)
升级 / 卸载
- 升级:
pnpm self-update(v12 转正前:pnpm self-update next-12) - 卸载:删除 CLI 相关文件(Windows 下
where.exe pnpm.*定位,删除pnpm.cmd/pnpm等)后重新安装或移除
关键操作(必读)
关键操作清单
npm install -g pnpm # 全局安装 pnpm
npm uninstall -g pnpm # 全局卸载 pnpm
which pnpm # 查看pnpm二进制程序的路径,如: /d/Program_Files/nodejs/node-v25.9.0-win-x64/pnpm
pnpm --version # 查看版本
pnpm install # 按 lockfile 安装(等价 pnpm i)
pnpm add <pkg> # 安装并写入 dependencies
pnpm add -D <pkg> # 开发依赖
pnpm remove <pkg> # 卸载
pnpm update # 按范围升级依赖
pnpm dlx <cmd> # 临时执行包命令(等价 npx)
pnpm config get globalconfig # 查看全局配置文件的存储路径
$ pnpm config get globalconfig
C:\Users\xxx\AppData\Local\pnpm\config\config.yaml
pnpm store path # 查看全局 store 路径
//方式1
$ pnpm store path
C:\Users\xxx\AppData\Local\pnpm\store\v11
//方式2: 借助 全局配置文件 C:\Users\xxx\AppData\Local\pnpm\config\config.yaml (此路径可通过`pnpm config get globalconfig`命令获取)的 storeDir 配置项,来查看
$ cat $(pnpm config get globalconfig)
storeDir: E:\Program-Data\pnpm-data\store
pnpm store prune # 清理孤儿包(回收磁盘)
pnpm approve-builds # 批准/管理依赖构建脚本白名单
pnpm env use <version> # 充当 Node 版本管理器
pnpm --filter <pkg> <script> # monorepo 中运行某子包脚本
pnpm --frozen-lockfile # CI 锁定安装
npm → pnpm 常见映射:npm install→pnpm install;npm install <pkg>→pnpm add <pkg>;npm run <script>→pnpm run <script>(或 pnpm <script>);npx <cmd>→pnpm dlx <cmd>。
pnpm config xxx 命令操作 (必读)
-
从配置中【获取/设置/删除】单项配置值,如 pnpm config get store-dir
-
- 配置优先级(从高到低):命令行参数 > 环境变量 > 项目根
.npmrc> 用户~/.npmrc> 全局配置文件 > 内置默认值。 config list显示的是合并后的最终结果,看不到 "某项来自哪个文件"—— 想溯源优先级可用pnpm config list --json结合不同级别分别查看。
- 配置优先级(从高到低):命令行参数 > 环境变量 > 项目根
pnpm config get <key> # 获取单个配置项
pnpm config get store-dir #
pnpm config get globalconfig # 查看全局配置文件的存储路径
pnpm config get registry # 查看默认 registry(兜底源) / 确认无 scope 的普通包从哪个源下载
pnpm config get registries # 查看 "哪个 scope 走哪个源" 的路由规则
pnpm config set <key> <v> # 设置(默认写用户级 ~/.npmrc)
pnpm config delete <key> # 删除某项
pnpm config list --global # 只列全局配置文件内容
pnpm config list --json # 列出配置,结合不同级别分别查看
------------ 样例: ↓
$ pnpm config get globalconfig # 查看全局配置文件的存储路径
C:\Users\xxx\AppData\Local\pnpm\config\config.yaml
$ pnpm config get registry
https://registry.npmmirror.com/
$ pnpm config get registries
{
"https://npm.jsr.io/": {
"scopes": [
"@jsr"
]
},
"https://npm.pkg.github.com/": {
"prefix": "gh"
},
"https://registry.npmjs.org/": {
"prefix": "npmjs"
},
"https://registry.npmmirror.com/": {
"scopes": [
"@"
]
}
}
$ pnpm config get store-dir
E:\Program-Data\pnpm-data\store\v11
$ pnpm config list --json
{
"@jsr:registry": "https://npm.jsr.io/",
"json": true,
"registries": {
"https://npm.jsr.io/": {
"scopes": [
"@jsr"
]
},
"https://npm.pkg.github.com/": {
"prefix": "gh"
},
"https://registry.npmjs.org/": {
"prefix": "npmjs"
},
"https://registry.npmmirror.com/": {
"scopes": [
"@"
]
}
},
"registry": "https://registry.npmmirror.com/",
"storeDir": "E:\\Program-Data\\pnpm-data\\store\\v11",
"userAgent": "pnpm/11.24.0 npm/? node/v25.9.0 win32 x64"
}
- 显示 pnpm 当前 "最终生效" 的完整配置
显示 pnpm 当前 "最终生效" 的完整配置(把所有来源 —— 命令行参数、环境变量、多级
.npmrc配置 —— 合并解析后的结果),输出为 JSON 对象,官方文档说明认证相关设置(token 等)不会显示。
$ pnpm config list
{
"@jsr:registry": "https://npm.jsr.io/",
"registries": {
"https://npm.jsr.io/": {
"scopes": [
"@jsr"
]
},
"https://npm.pkg.github.com/": {
"prefix": "gh"
},
"https://registry.npmjs.org/": {
"prefix": "npmjs"
},
"https://registry.npmmirror.com/": {
"scopes": [
"@"
]
}
},
"registry": "https://registry.npmmirror.com/",
"storeDir": "E:\\Program-Data\\pnpm-data\\store\\v11",
"userAgent": "pnpm/11.24.0 npm/? node/v25.9.0 win32 x64"
}
案例实践
CASE 基于pnpm编译/构建项目(deepseek-hardness项目为例)(必读)
# 安装 corepack : 装到 npm 全局目录(`%APPDATA%\npm`),装完需【重开一个终端】让 PATH 生效;之后项目 `package.json` 若有 `packageManager` 字段,corepack 会自动锁定并切换对应 pnpm 版本。
npm install -g corepack
# 查验 pnpm 是否已安装 | 若未安装,则安装之: npm install -g pnpm (可考虑配置 store / cache / state / bin 等目录到除C盘之外的其他盘,避免C盘系统盘后期越加臃肿)
pnpm --version
----------
# 克隆代码到本地
git clone https://github.com/deepseek-ai/deepseek-harness.git
# 进入项目根目录,之后命令都在该项目作用域内执行(pnpm 在哪个目录执行,就装哪个目录的依赖)
cd deepseek-harness
# 启用 Corepack(Node 自带的 "包管理器管理器"):
## 它会在 `node` 命令旁创建 `pnpm` 的 shim;
### 此后每次敲 `pnpm`,它会自动读取本项目 `package.json` 里的 `packageManager` 字段(如 `pnpm@9.x.x`)、并【切换到该精确版本】(本地没有会自动下载),保证团队 / CI 用同一个 pnpm 版本
## corepack 只是 "帮你安装 / 锁定 pnpm 版本" 的额外工具,不是运行 deepseek-harness 的必需项
### Corepack 只随 Node 14.19.0 ~ <25.0.0 分发,node版本不在此范围的,可跳过此步骤 ——> 可直接跳过 corepack,用 pnpm 就行(推荐)
corepack enable
# 按当前项目清单安装依赖(等价 pnpm i)
pnpm install
# 执行 `package.json` 中 `scripts.build` 定义的构建脚本(通常产出生产构建产物到 dist/)
pnpm run build
# 启动 Web UI(端口 3080) | 等价 `pnpm exec dsh web`:
## 在项目范围内执行 `node_modules/.bin` 里的 `dsh` 命令(deepseek-harness 自带 CLI),`web` 是其子命令,启动端口 3080 的 Web UI。
## 官方机制:命令不与 pnpm 内置命令冲突时,`exec` 可省略
pnpm dsh web
- 推荐文献
参考文献
- 统一 pnpm 的存储位置 - CSDN 2025.03.22
- pnpm老是默认把包安装在C盘很头疼?教你快速配置pnpm的全局目录 - 掘金 2025.11.29
Z FAQ for pnpm 进阶篇
Q: JavaScript / TypeScript / Node.js / npm / npx / pnpm 的联系与区别?*(必读)
- [Web前端/Javascript/TypeScript/包管理] 辨析:JavaScript / TypeScript / Node.js / npm / npx / pnpm - 博客园/千千寰宇
Q: pnpm 的 node_modules 和 npm 有什么不同?*(必读)
- npm 采用扁平提升:所有(含间接)依赖都被提升到
node_modules根目录,源码可访问未声明依赖(幽灵依赖)。 - pnpm 默认非扁平:根目录只放直接依赖的符号链接,真实文件以硬链接存在于
.pnpm虚拟 store,间接依赖不暴露给源码。 - 如工具链不兼容符号链接,可设
nodeLinker=hoisted回退为扁平结构。
Q: pnpm install 的运行过程?*(必读)
它读取哪些 "依赖清单" 输入文件?
| 文件 | 作用(优先级) |
|---|---|
| package.json | 核心依赖清单:声明 dependencies、devDependencies、optionalDependencies、peerDependencies、scripts、packageManager 等 |
| pnpm-lock.yaml | 锁文件:记录解析后的精确版本与依赖树。存在且与 package.json 一致 → 按它精确安装(确定性);不存在 / 过期 → 重新解析并生成 / 更新 |
| pnpm-workspace.yaml | 若存在,说明是 monorepo,声明工作区包含哪些包 |
.npmrc / 配置项 |
影响 "从哪个源下载(registry)、存到哪(store-dir)",属于配置,不是依赖清单本身 |
- 安装的三阶段(官方定义)
- 依赖解析(Resolution):读
package.json+pnpm-lock.yaml,构建依赖树、确定每个包版本;识别全局 store 中缺失的包,从 registry 拉取(存入 store)。- 目录结构计算(Directory structure calculation):根据解析结果算出
node_modules应长什么样(哪些包、什么层级)。- 链接依赖(Linking):已缓存包直接硬链接到
node_modules/.pnpm,再通过符号链接把直接依赖暴露到顶层 —— 全程不重复下载。
- 关键细节
- 有 lockfile 时走 "精确安装",保证不同机器结果一致;CI 常用
pnpm install --frozen-lockfile(lockfile 与 package.json 不一致就直接报错,而不是悄悄更新)。- pnpm 11 还会校验
package.json的packageManager与当前 pnpm 版本是否匹配(可与前面步骤中的corepack enable配合,形成 "版本锁定 + 依赖锁定" 双保险)。
Q: pnpm 为什么会更省磁盘、更快?(必读)
- 省磁盘:内容寻址存储 + 硬链接,同一版本依赖全局只存一份、跨项目共享;不同版本只存差异文件。
- 更快:安装分"解析—算结构—链接"三阶段,已缓存依赖直接从 store 硬链接,无需重复下载解压;官网称快至 2 倍。
Q: pnpm 能用 npx 吗?它的 npx 对应物是什么?*
pnpm dlx <cmd>即 npx 的对应物,用于临时执行包命令而不全局安装。- npm 生态的 npx 也能执行 pnpm 相关命令(如
npx get-pnpm)。
Q: pnpm 安装报 "Cannot find module .../pnpm.js" 怎么办?
多因 pnpm 损坏或 PATH 残留。定位 which pnpm(Windows: where.exe pnpm.*),删除目录下 pnpm.cmd/pnpm 等残留文件后重装。
Q: Windows 下 pnpm 安装很慢 / 被 Defender 拦截?
Defender 会显著拖慢安装并可能拦截独立脚本产物。官方建议 Windows 用 npm 安装;也可用管理员 PowerShell 执行 Add-MpPreference -ExclusionPath $(pnpm store path) 将 store 加入排除。
Q: pnpm 12 和 11 有什么区别?要不要迁移?
pnpm 12 是 Rust 重写,命令、flag、设置与锁文件格式与 11 完全兼容,官方定位为"非迁移式":通常升级 v11.10+ 后执行 pnpm self-update next-12(转正后 pnpm self-update)即可,行为差异仅集中在少数点(见官方 "What's different in pnpm 12")。
v12 安装后不依赖 Node,Intel macOS 也能用独立脚本。
Z FAQ for pnpm 具体操作
Q: pnpm 默认的node‑linker类型为isolated(将基于软链接机制),但磁盘的文件系统类型为exFAT时,将不支持创建软链接,需退到hoisted模式,否则报:The "H:" drive is exFAT, which does not support symlinks. This will cause installation to fail. You can set the node-linker to "hoisted" to avoid this issue.(必读)
问题描述 + 原因分析
- 问题描述
pnpm EISDIR symlink 报错 exFAT磁盘问题
PS H:\Github\deepseek-harness> pnpm install
...
Downloading @openai/codex@0.149.1-win32-x64: 139.92 MB/139.92 MB, done
[ERR_PNPM_EISDIR] [symlinkAllModules] EISDIR: illegal operation on a directory, symlink '..\..\..\@eslint-community+eslint-ut_ce220ce26768997fff88d9f93c9d53c7\node_modules\@eslint-community\eslint-utils' -> 'H:\Github\deepseek-harness\node_modules\.pnpm\@stylistic+eslint-plugin@5.10.0_eslint@10.5.0_jiti@2.7.0_\node_modules\@eslint-community\eslint-utils'
The "H:" drive is exFAT, which does not support symlinks. This will cause installation to fail. You can set the node-linker to "hoisted" to avoid this issue.
Progress: resolved 1001, reused 0, downloaded 996, added 1001
- 原因分析
报错核心原因:H盘是
exFAT文件系统,Windows下exFAT不支持【符号链接】(symlink),pnpm默认使用isolatednode‑linker,大量创建软链接,直接失败。
提示原文也给出方案:设置
node-linker=hoisted
解决方案
方案1:项目内修改(推荐,只对当前项目生效)
在项目根目录新建/修改 .npmrc,写入:
node-linker=hoisted
然后,重新执行
pnpm install
方案2:命令行临时指定
pnpm install --node-linker hoisted
方案3:全局pnpm配置(所有项目都生效)
pnpm config set node-linker hoisted --global
补充:清理旧的损坏缓存
安装失败残留文件建议先清理,避免干扰:
# 删除项目node_modules与pnpm锁
Remove-Item -Recurse -Force node_modules
Remove-Item pnpm-lock.yaml
pnpm install
补充说明
hoisted模式:不再大量使用软链接,依赖提升平铺,兼容exFAT/FAT32,代价是少量依赖扁平化,和 npm/yarn 行为接近,绝大多数项目无副作用。- 如果你可以把项目迁移到 NTFS磁盘,则可以继续使用默认
isolated模式(pnpm原生最优模式)。 - Windows开发者注意:
exFAT多用于U盘/移动硬盘,本身对符号链接支持残缺,不适合存放node项目。
为什么报 EISDIR: illegal operation on a directory, symlink
不是文件损坏,是底层系统调用失败:pnpm尝试创建软链接,exFAT直接拒绝该系统API,抛出此错误码。
备选:如果 hoisted 之后还有报错
可以再加一行到 .npmrc
node-linker=hoisted
symlink=false
hoisted模式下 pnpm 的硬链接/内容寻址存储优势会减弱,但可以保证在exFAT磁盘正常跑项目。
Y 推荐文献
- pnpm - 官方网站(中文文档)
- pnpm Motivation(项目初衷:为什么做 pnpm)
- Symlinked node_modules structure(符号链接结构详解)
- pnpm Feature comparison(与 npm/Yarn 官方功能对比)
- pnpm 12.0 发布博客(Rust 重写说明)
- pnpm - GitHub 仓库
- pnpm教程 - 菜鸟教程
X 参考文献
- pnpm - 官方网站
- 项目初衷 | pnpm
- 功能比较 | pnpm
- 安装 | pnpm
- pnpm - GitHub
- pnpm - npm
- pnpm/pnpm - gstars.dev(Star/Fork/贡献者统计)
- pnpm - Release Alert(版本发布频率)
- pnpm vs npm vs Yarn in 2026 - devtoolswatch
- pnpm 的详细设计与 npm 对比 - CSDN
- npm、yarn、pnpm 的区别与介绍 - 掘金
- 完整教程:npm、pnpm、npx 三者的定位与差异 - 博客园
- TypeScript 术语表 - MDN
- TypeScript 官方网站(typed superset of JavaScript)
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号