[前端/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

开源与许可: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 installpnpm addpnpm updatepnpm 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:记录精确解析结果,保证确定性

架构与运行原理

核心数据流(图示):

flowchart LR subgraph 全局store["全局内容寻址存储 (store)"] P1["包 A@1.0.0 (内容哈希)"] P2["包 B@2.0.0 (内容哈希)"] P3["包 C@1.0.0 (内容哈希)"] end subgraph 项目X["项目 X 的 node_modules"] NX1["node_modules/A (符号链接)"] NX2["node_modules/.pnpm/... (硬链接到 store)"] end subgraph 项目Y["项目 Y 的 node_modules"] NY1["node_modules/A (符号链接)"] NY2["node_modules/.pnpm/... (硬链接到 store)"] end P1 -- "硬链接" --> NX2 P1 -- "硬链接" --> NY2 P2 -- "硬链接" --> NX2 NX1 -- "符号链接指向 .pnpm 内真实包" --> P1 NY1 -- "符号链接指向 .pnpm 内真实包" --> P1

一次 pnpm install 的三阶段

阶段 动作
① 依赖解析 识别 store 中缺失的包并拉取到全局 store
② 目录结构计算 根据依赖树计算 node_modules 布局
③ 链接依赖项 已缓存包直接从 store 硬链接到 node_modules,无需重新下载

运行要点

  1. 一次下载、处处复用:所有包文件只存在于全局 store,多个项目共享同一版本 = 共享同一批硬链接,磁盘零冗余。
  2. 只存差异:同一包的不同版本,仅把发生变化的文件加入 store(例如某包 100 个文件升级只改 1 个,则只新增 1 个文件)。
  3. 顶层只放直接依赖node_modules 根目录下的包是符号链接(指向 .pnpm/<name>@<version>/node_modules/<name>),源码只能 require 到声明过的依赖,杜绝幽灵依赖。
  4. 严格、确定性pnpm-lock.yaml 锁定精确版本与结构;CI 用 --frozen-lockfile 拒绝漂移。
  5. 安全默认:依赖的 postinstall 等构建脚本默认不自动执行(pnpm approve-builds 白名单放行),降低供应链风险。
  6. 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 installpnpm installnpm 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项目为例)(必读)

https://github.com/deepseek-ai/deepseek-harness

# 安装 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
  • 推荐文献

参考文献

Z FAQ for pnpm 进阶篇

Q: 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)",属于配置,不是依赖清单本身
  • 安装的三阶段(官方定义)
  1. 依赖解析(Resolution):读 package.json + pnpm-lock.yaml,构建依赖树、确定每个包版本;识别全局 store 中缺失的包,从 registry 拉取(存入 store)。
  2. 目录结构计算(Directory structure calculation):根据解析结果算出 node_modules 应长什么样(哪些包、什么层级)。
  3. 链接依赖(Linking):已缓存包直接硬链接node_modules/.pnpm,再通过符号链接把直接依赖暴露到顶层 —— 全程不重复下载。
  • 关键细节
  • 有 lockfile 时走 "精确安装",保证不同机器结果一致;CI 常用 pnpm install --frozen-lockfile(lockfile 与 package.json 不一致就直接报错,而不是悄悄更新)。
  • pnpm 11 还会校验 package.jsonpackageManager 与当前 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 具体操作

问题描述 + 原因分析

  • 问题描述

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 默认使用 isolated node‑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

补充说明

  1. hoisted 模式:不再大量使用软链接,依赖提升平铺,兼容 exFAT / FAT32,代价是少量依赖扁平化,和 npm/yarn 行为接近,绝大多数项目无副作用
  2. 如果你可以把项目迁移到 NTFS磁盘,则可以继续使用默认 isolated 模式(pnpm原生最优模式)。
  3. Windows开发者注意:exFAT多用于U盘/移动硬盘,本身对符号链接支持残缺,不适合存放node项目。

不是文件损坏,是底层系统调用失败:pnpm尝试创建软链接,exFAT直接拒绝该系统API,抛出此错误码。

备选:如果 hoisted 之后还有报错

可以再加一行到 .npmrc

node-linker=hoisted
symlink=false

hoisted模式下 pnpm 的硬链接/内容寻址存储优势会减弱,但可以保证在exFAT磁盘正常跑项目。

Y 推荐文献

X 参考文献

posted @ 2026-08-31 23:39  千千寰宇  阅读(10)  评论(0)    收藏  举报