找回密码
 立即注册
首页 业界区 业界 我要成为node_modules大师!(一):包管理器选择,依赖关系 ...

我要成为node_modules大师!(一):包管理器选择,依赖关系分析

厂潺 2025-6-3 10:45:53
好家伙

1.npm曾经的一些问题

1. 依赖地狱(Dependency Hell)


  • 嵌套依赖结构:早期版本的 npm 采用嵌套的 node_modules 结构,依赖层级极深,容易导致路径过长问题(尤其在 Windows 上),甚至触发文件系统限制。
  • 版本冲突:依赖的版本管理不够严格,容易出现“同一个包多个版本”共存的情况,导致项目体积膨胀或难以调试。
2. 性能问题


  • 安装速度慢:npm 的安装算法(尤其是 v3 之前)效率较低,依赖解析和下载时间较长。
  • 全局锁问题:npm 的锁文件(package-lock.json)设计曾被诟病与其他工具(如 Yarn)不兼容,且早期版本存在锁文件冲突问题。
3. 安全性历史问题


  • 依赖链风险:npm 允许依赖包自动安装任意子依赖,曾引发多起安全事件(例如 event-stream 恶意包注入事件)。
  • 权限问题:过去 npm 的包发布机制容易被滥用,出现过“包名抢注”(squatting)或低质量包泛滥的情况。
4. 设计哲学争议


  • 集中式 registry:npm 的官方 registry 是单点故障,一旦宕机(如 2020 年的服务中断),全球开发者受影响。
  • 语义化版本(SemVer)的滥用:许多包过度依赖 ^ 或 ~ 版本范围,导致不同环境安装的依赖版本不一致,可能引发意外问题。
5. 竞争对手的对比


  • Yarn 的冲击:Yarn 在 2016 年推出后,凭借离线缓存、并行安装、更稳定的锁文件等特性,直接暴露了 npm 的短板。
  • pnpm 的改进:pnpm 通过硬链接和符号链接优化存储空间和安装速度,进一步凸显了 npm 的冗余问题。
 

尽管如此

对于大多数普通项目,npm 已足够稳定,尤其是新版(v7+)吸收了 Yarn 和 pnpm 的优点。

2.包管理工具


npm官方默认,兼容性无敌
Yarn稳定可靠,锁文件严谨
pnpm省空间、快、无依赖冲突
Bun宇宙最快,All-in-One




 
 
 
3.具体的依赖关系实例分析

现在 有两个项目,
项目1,依赖需求: a,b,c a依赖于b,c , c无依赖依赖
项目2.依赖需求: a,b,c,d a依赖于b,c , c依赖于d ,d依赖于b
这是两个典型项目,
第一个,代表直接依赖
第二个,代表嵌套依赖
现在我分别使用npm,yarn,pnpm,bun,

我们分别分析器其node_modules文件夹结构,以及package文件,和lock文件
 
3.1.第一个项目非常简单

安装结果对比

包管理器node_modules 结构锁文件格式npm扁平化(hoisting):
- a, b, c(顶层)
- a/node_modules 无嵌套(依赖已提升)package-lock.json(嵌套结构,标记依赖来源)Yarn类似 npm 的扁平化:
- a, b, c(顶层)
- 无重复依赖yarn.lock(扁平列表,记录所有依赖的精确版本)pnpm隔离结构:
- 顶层只有 a, b, c(符号链接)
- 真实依赖存储在 ~/.pnpm-store,通过硬链接引用pnpm-lock.yaml(内容寻址,记录依赖的存储路径)Bun类似 pnpm 的硬链接优化:
- 扁平化但共享依赖存储
- 依赖通过硬链接复用bun.lockb(二进制锁文件,记录依赖树和哈希) 
 
 
 
 
 
 
 
 
 
3.2.我们重点关注第二个项目

看看各家工具如何处理嵌套依赖
包管理器node_modules 结构关键区别npm扁平化 + 部分嵌套:
- a, b, c, d(顶层)
- 如果 b 有多个版本,低版本会嵌套在 d/node_modulespackage-lock.json 会标记 d 的 b 是否嵌套Yarn完全扁平化:
- a, b, c, d(顶层)
- 若版本冲突,Yarn 会选择一个版本,可能导致问题yarn.lock 会记录所有依赖的解析版本pnpm严格隔离:
- a, b, c, d(顶层符号链接)
- c 和 d 的 b 不会冲突,各自引用正确版本pnpm-lock.yaml 会记录每个包的独立存储路径Bun类似 pnpm:
- 共享存储 + 硬链接
- 依赖版本冲突时,Bun 会优先兼容bun.lockb 会优化存储,避免重复



 
 
 
 
 
 
 
 
 
来看示例图:
(1)NPM
node_modules
  1. <strong>node_modules/
  2. ├── a/               # a@1.0.0
  3. │   └── package.json # 依赖: b, c
  4. ├── b/               # b@1.0.0 (被 a 和 d 依赖)
  5. ├── c/               # c@1.0.0
  6. │   └── package.json # 依赖: d
  7. ├── d/               # d@1.0.0
  8. │   └── package.json # 依赖: b
  9. └── .bin/            # 可执行文件(如果有)</strong>
复制代码
锁文件
package-lock.json
  1. <strong>{
  2.   "name": "project2",
  3.   "version": "1.0.0",
  4.   "lockfileVersion": 2,
  5.   "requires": true,
  6.   "packages": {
  7.     "node_modules/a": {
  8.       "version": "1.0.0",
  9.       "dependencies": { "b": "^1.0.0", "c": "^1.0.0" }
  10.     },
  11.     "node_modules/b": { "version": "1.0.0" },
  12.     "node_modules/c": {
  13.       "version": "1.0.0",
  14.       "dependencies": { "d": "^1.0.0" }
  15.     },
  16.     "node_modules/d": {
  17.       "version": "1.0.0",
  18.       "dependencies": { "b": "^1.0.0" }
  19.     }
  20.   }
  21. }</strong>
复制代码
(2)Yarn
node_modules
  1. <strong>node_modules/
  2. ├── a/               # a@1.0.0
  3. │   └── package.json # 依赖: b, c
  4. ├── b/               # b@1.0.0 (提升到顶层)
  5. ├── c/               # c@1.0.0
  6. │   └── package.json # 依赖: d
  7. ├── d/               # d@1.0.0
  8. │   └── package.json # 依赖: b
  9. └── .bin/</strong>
复制代码
锁文件
yarn.lock
  1. <strong># THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
  2. a@1.0.0:
  3.   version "1.0.0"
  4.   dependencies:
  5.     b "^1.0.0"
  6.     c "^1.0.0"
  7. b@1.0.0:
  8.   version "1.0.0"
  9. c@1.0.0:
  10.   version "1.0.0"
  11.   dependencies:
  12.     d "^1.0.0"
  13. d@1.0.0:
  14.   version "1.0.0"
  15.   dependencies:
  16.     b "^1.0.0"</strong>
复制代码
(3)pnpm
node_modules
  1. <strong>node_modules/
  2. ├── a -> .pnpm/a@1.0.0/node_modules/a      # 符号链接
  3. ├── b -> .pnpm/b@1.0.0/node_modules/b
  4. ├── c -> .pnpm/c@1.0.0/node_modules/c
  5. ├── d -> .pnpm/d@1.0.0/node_modules/d
  6. └── .pnpm/
  7.     ├── a@1.0.0/
  8.     │   └── node_modules/
  9.     │       ├── a       # a 的真实文件
  10.     │       ├── b -> ../../b@1.0.0/node_modules/b  # 硬链接
  11.     │       └── c -> ../../c@1.0.0/node_modules/c
  12.     ├── c@1.0.0/
  13.     │   └── node_modules/
  14.     │       ├── c       # c 的真实文件
  15.     │       └── d -> ../../d@1.0.0/node_modules/d
  16.     ├── d@1.0.0/
  17.     │   └── node_modules/
  18.     │       ├── d       # d 的真实文件
  19.     │       └── b -> ../../b@1.0.0/node_modules/b  # 硬链接
  20.     └── b@1.0.0/
  21.         └── node_modules/
  22.             └── b       # b 的真实文件</strong>
复制代码
锁文件
pnpm-lock.yaml
  1. <strong>lockfileVersion: 5.4
  2. dependencies:
  3.   a:
  4.     specifier: 1.0.0
  5.     version: 1.0.0
  6.     dependencies:
  7.       b: 1.0.0
  8.       c: 1.0.0
  9.   b:
  10.     specifier: 1.0.0
  11.     version: 1.0.0
  12.   c:
  13.     specifier: 1.0.0
  14.     version: 1.0.0
  15.     dependencies:
  16.       d: 1.0.0
  17.   d:
  18.     specifier: 1.0.0
  19.     version: 1.0.0
  20.     dependencies:
  21.       b: 1.0.0</strong>
复制代码
(4)bun
  1. <strong>node_modules/
  2. ├── a/               # a@1.0.0 (硬链接到全局存储)
  3. ├── b/               # b@1.0.0 (硬链接)
  4. ├── c/               # c@1.0.0
  5. ├── d/               # d@1.0.0
  6. └── .bin/</strong>
复制代码
锁文件
bun.lockb
为二进制

总结,
特性npmYarnpnpmBun依赖结构扁平化(可能嵌套冲突)完全扁平化(可能版本冲突)隔离 + 硬链接(无冲突)扁平化 + 硬链接优化安装速度较快最快(复用存储)极快(内置优化)磁盘占用高(每个项目独立存储)较高极低(全局共享存储)低(共享存储)锁文件格式package-lock.json(嵌套)yarn.lock(扁平列表)pnpm-lock.yaml(内容寻址)bun.lockb(二进制高效)幻影依赖严重(依赖提升)存在无(严格隔离)较少(但比 pnpm 宽松)


 
 
 
 
 
 
4.一个问题

对于项目2提出一个新的情况

假设项目本身依赖的b包为1.0.0
d包依赖的b包版本为:2.0.0

node_modules和锁文件会发生什么?
  1. <strong>node_modules/
  2. ├── a/               # a@1.0.0
  3. │   └── package.json # 依赖: b@1.0.0, c@1.0.0
  4. ├── b/               # b@1.0.0 (被提升到顶层)
  5. ├── c/               # c@1.0.0
  6. │   └── package.json # 依赖: d@1.0.0
  7. ├── d/               # d@1.0.0
  8. │   ├── node_modules/
  9. │   │   └── b/       # b@2.0.0 (嵌套)
  10. │   └── package.json # 依赖: b@2.0.0
  11. └── .bin/</strong>
复制代码
 
由于依赖提升,所以b包版本1.0.0(先遇到)于是,被提升到顶层
npm 会尽量将依赖提升到顶层,但同一包的不同版本只能提升一个,其余版本会嵌套。



 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册