一、扁平化安装
-
默认行为:无论依赖层级多深,相同版本的包都会被提升到
node_modules根目录。 -
示例:
- 项目直接依赖
A@1.0.0和B@1.0.0 A依赖lodash@4.17.21,B也依赖lodash@4.17.21- 最终结构:
- 项目直接依赖
node_modules/
├── A/
├── B/
└── lodash/ # 被提升到根目录,而不是藏在 A/node_modules 或 B/node_modules 中
常见问题
1. 幽灵依赖
-
定义:未在
package.json中声明,但由于扁平化安装而可用的依赖。 -
示例:
- 项目依赖
A@1.0.0 A依赖lodash@4.17.21- 由于扁平化,
lodash被提升到根目录 - 开发者可以直接在代码中引入
lodash,即使没有在package.json中声明:
- 项目依赖
// 未在 package.json 中声明 lodash,但可以引入
import _ from 'lodash'; // 不会报错!
-
风险:
- 当
A更新依赖(如改用lodash@5.x),幽灵依赖可能突然失效。 - 新环境安装时,依赖结构可能变化,导致幽灵依赖不可用。
- 当
2. 磁盘空间浪费
- 当多个项目依赖
lodash@4.17.21时,每个项目的node_modules都会完整解压一份。 - 100 个项目 = 100 份相同文件,浪费大量磁盘空间。
npm/yarn的安装机制就是扁平化安装,即存在幽灵依赖的问题
二、非扁平化安装
-
默认行为: 所有依赖只在全局store中存储一次,且项目依赖按层级存储在
node_modules的.pnpm目录中,通过硬链接链接到全局store,包含完整的版本信息,node_modules只存储包含package.json中声明的依赖的符号链接。 -
示例:
node_modules/
├── .pnpm/
│ ├── axios@1.6.2/
│ │ └── node_modules/
│ │ └── axios/ # 实际文件
│ ├── lodash@4.17.21/
│ │ └── node_modules/
│ │ └── lodash/ # 实际文件
│ └── react@18.2.0/
│ └── node_modules/
│ └── react/ # 实际文件
├── axios -> .pnpm/axios@1.6.2/node_modules/axios # 符号链接
└── react -> .pnpm/react@18.2.0/node_modules/react # 符号链接
# lodash 未在 package.json 中声明,不会出现在根目录
非扁平化安装不会存在幽灵依赖的问题,因为根目录node_modules只会存在项目package.json中定义好的依赖符号链接,且磁盘占用率小,因为同一个包同一版本只会在磁盘上存储一份,不同项目通过硬链接引用即可,像pnpm的安装机制就是非扁平化安装。