扁平化安装与非扁平化安装

144 阅读2分钟

一、扁平化安装

  • 默认行为:无论依赖层级多深,相同版本的包都会被提升到 node_modules 根目录。

  • 示例

    • 项目直接依赖 A@1.0.0 和 B@1.0.0
    • A 依赖 lodash@4.17.21B 也依赖 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的安装机制就是非扁平化安装。