node-linker 和 shamefully-hoist 都是 pnpm 的配置项,但它们控制的是完全不同层面的事情。简单来说:
node-linker决定node_modules的整体结构布局(宏观架构)。shamefully-hoist决定哪些依赖包会被提升到根目录(微观可见性)。
下面我为你详细拆解,并说明它们之间的交互关系。
1. node-linker:决定 node_modules 的"骨架"
node-linker 定义了 pnpm 将依赖物理链接到磁盘上的方式。它有三个可选值:
| 值 | 行为 | node_modules 结构 |
|---|---|---|
isolated(默认) |
使用虚拟存储 (node_modules/.pnpm)和符号链接。所有依赖都硬链接到全局存储,再软链接到项目中。 |
根目录只有 package.json 中声明的直接依赖,子依赖都在 .pnpm 文件夹内。 |
hoisted |
模仿 npm 的扁平化结构。完全放弃 .pnpm 虚拟存储,将依赖平铺在根目录。 |
根目录充满所有依赖(直接依赖 + 子依赖),没有 .pnpm 文件夹。 |
pnp |
使用 Yarn 的 Plug'n'Play 策略,不生成 node_modules 目录,而是通过 .pnp.cjs 文件映射依赖。 |
没有 node_modules 目录。 |
核心作用:它决定了 pnpm 节省磁盘空间(硬链接)和严格隔离依赖的能力。
2. shamefully-hoist:决定"可见性"的开关
shamefully-hoist 是一个布尔值(true / false),它只作用于 node-linker = isolated(默认模式) 下。
| 值 | 行为 | 效果 |
|---|---|---|
false(默认) |
只将 package.json 中声明的直接依赖 放在根目录,子依赖锁在 .pnpm 中。 |
项目代码不能 直接引用未在 package.json 中声明的包(严格模式,无"幽灵依赖")。 |
true |
强制把所有依赖 (包括深层子依赖)都提升到根目录的 node_modules 中。 |
项目代码可以直接引用任何包(放宽了限制,引入了"幽灵依赖")。 |
核心作用:它解决了某些老旧工具或错误配置无法正确寻找深层依赖的问题(兼容性补丁)。
3. 两者的核心区别
| 对比维度 | node-linker |
shamefully-hoist |
|---|---|---|
| 控制的层面 | 宏观架构(包怎么存 、怎么链接) | 微观可见性(包是否暴露在根目录) |
| 影响范围 | 改变整个 node_modules 的文件夹结构 |
仅改变根目录下的文件列表(不影响 .pnpm 内部结构) |
| 是否改变存储方式 | 是 (isolated 用硬链接节省空间,hoisted 则放弃该优势) |
否(无论 true/false,包依然从全局存储硬链接而来) |
| 主要用途 | 在"极致性能/空间"和"传统 npm 兼容性"之间做选择 | 针对特定工具的兼容性问题"打补丁" |
4. 两者的交互关系(关键)
这两者不是互斥的,而是可以组合使用的。理解下面的组合场景至关重要:
场景一:默认组合(推荐)
ini
ini
node-linker = isolated
shamefully-hoist = false
- 结果:标准的 pnpm 严格模式。项目无法访问"幽灵依赖",磁盘占用最小。
- 适用:绝大多数现代项目。
场景二:放宽根目录可见性(常见兼容方案)
ini
ini
node-linker = isolated
shamefully-hoist = true
- 结果 :保留了 pnpm 的
.pnpm虚拟存储和硬链接优势(节省空间),但让node_modules根目录变得像 npm 一样"拥挤"(所有依赖都可见)。 - 适用 :项目遇到了某个工具找不到依赖的报错,但你不想放弃 pnpm 的存储优化。
- 注意 :这等同于设置
public-hoist-pattern = *。
场景三:完全模仿 npm(激进兼容)
ini
ini
node-linker = hoisted
# shamefully-hoist 无论设什么值都无效
- 结果 :pnpm 彻底放弃 自己的
.pnpm架构,直接生成和 npm 一模一样的扁平node_modules目录。 - 影响:失去了 pnpm 节省磁盘空间和安装速度快的核心优势。
- 注意 :当
node-linker = hoisted时,shamefully-hoist参数完全失效,因为所有依赖本来就已经在根目录了。
5. 如何选择?(最佳实践建议)
-
优先保持默认 :
node-linker = isolated且shamefully-hoist = false。这是 pnpm 设计的初衷。 -
遇到"幽灵依赖"报错时:
-
不要 直接开启
shamefully-hoist = true(这相当于开了一扇大门)。 -
更好的做法 :使用
public-hoist-pattern精确指定需要暴露的包,例如:ini
arduinopublic-hoist-pattern[] = *eslint* public-hoist-pattern[] = *plugin*
-
-
只有在极少数无法解决的场景下 (如某些老旧脚手架硬编码了
node_modules/xxx路径),才考虑:- 先试
shamefully-hoist = true(保留性能)。 - 最后万不得已才用
node-linker = hoisted(放弃 pnpm 优势)。
- 先试
总结一句话
node-linker决定 "怎么搭架子" (隔离存储还是平铺)。shamefully-hoist决定 "架子上的哪些东西摆到门口" (可见范围)。
绝大多数情况下,请保持默认的 isolated,并通过 public-hoist-pattern 做精准控制,而非一刀切地开启 shamefully-hoist 或切换 node-linker。