好家伙

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):
abc(顶层)
a/node_modules 无嵌套(依赖已提升)
package-lock.json(嵌套结构,标记依赖来源)
Yarn 类似 npm 的扁平化:
abc(顶层)
- 无重复依赖
yarn.lock(扁平列表,记录所有依赖的精确版本)
pnpm 隔离结构:
- 顶层只有 abc(符号链接)
- 真实依赖存储在 ~/.pnpm-store,通过硬链接引用
pnpm-lock.yaml(内容寻址,记录依赖的存储路径)
Bun 类似 pnpm 的硬链接优化:
- 扁平化但共享依赖存储
- 依赖通过硬链接复用
bun.lockb(二进制锁文件,记录依赖树和哈希)

 

 

 

 

 

 

 

 

 

 

3.2.我们重点关注第二个项目

看看各家工具如何处理嵌套依赖

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

 

来看示例图:

1.npm

(1)NPM

node_modules

node_modules/
├── a/ # a@1.0.0
│ └── package.json # 依赖: b, c
├── b/ # b@1.0.0 (被 a 和 d 依赖)
├── c/ # c@1.0.0
│ └── package.json # 依赖: d
├── d/ # d@1.0.0
│ └── package.json # 依赖: b
└── .bin/ # 可执行文件(如果有)

锁文件

package-lock.json

{
"name": "project2",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/a": {
"version": "1.0.0",
"dependencies": { "b": "^1.0.0", "c": "^1.0.0" }
},
"node_modules/b": { "version": "1.0.0" },
"node_modules/c": {
"version": "1.0.0",
"dependencies": { "d": "^1.0.0" }
},
"node_modules/d": {
"version": "1.0.0",
"dependencies": { "b": "^1.0.0" }
}
}
}

(2)Yarn

node_modules

node_modules/
├── a/ # a@1.0.0
│ └── package.json # 依赖: b, c
├── b/ # b@1.0.0 (提升到顶层)
├── c/ # c@1.0.0
│ └── package.json # 依赖: d
├── d/ # d@1.0.0
│ └── package.json # 依赖: b
└── .bin/

锁文件

yarn.lock

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
a@1.0.0:
version "1.0.0"
dependencies:
b "^1.0.0"
c "^1.0.0" b@1.0.0:
version "1.0.0" c@1.0.0:
version "1.0.0"
dependencies:
d "^1.0.0" d@1.0.0:
version "1.0.0"
dependencies:
b "^1.0.0"

(3)pnpm

node_modules

node_modules/
├── a -> .pnpm/a@1.0.0/node_modules/a # 符号链接
├── b -> .pnpm/b@1.0.0/node_modules/b
├── c -> .pnpm/c@1.0.0/node_modules/c
├── d -> .pnpm/d@1.0.0/node_modules/d
└── .pnpm/
├── a@1.0.0/
│ └── node_modules/
│ ├── a # a 的真实文件
│ ├── b -> ../../b@1.0.0/node_modules/b # 硬链接
│ └── c -> ../../c@1.0.0/node_modules/c
├── c@1.0.0/
│ └── node_modules/
│ ├── c # c 的真实文件
│ └── d -> ../../d@1.0.0/node_modules/d
├── d@1.0.0/
│ └── node_modules/
│ ├── d # d 的真实文件
│ └── b -> ../../b@1.0.0/node_modules/b # 硬链接
└── b@1.0.0/
└── node_modules/
└── b # b 的真实文件

锁文件

pnpm-lock.yaml

lockfileVersion: 5.4
dependencies:
a:
specifier: 1.0.0
version: 1.0.0
dependencies:
b: 1.0.0
c: 1.0.0
b:
specifier: 1.0.0
version: 1.0.0
c:
specifier: 1.0.0
version: 1.0.0
dependencies:
d: 1.0.0
d:
specifier: 1.0.0
version: 1.0.0
dependencies:
b: 1.0.0

(4)bun

node_modules/
├── a/ # a@1.0.0 (硬链接到全局存储)
├── b/ # b@1.0.0 (硬链接)
├── c/ # c@1.0.0
├── d/ # d@1.0.0
└── .bin/

锁文件

bun.lockb
为二进制

总结,

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

4.一个问题

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

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

node_modules和锁文件会发生什么?

node_modules/
├── a/ # a@1.0.0
│ └── package.json # 依赖: b@1.0.0, c@1.0.0
├── b/ # b@1.0.0 (被提升到顶层)
├── c/ # c@1.0.0
│ └── package.json # 依赖: d@1.0.0
├── d/ # d@1.0.0
│ ├── node_modules/
│ │ └── b/ # b@2.0.0 (嵌套)
│ └── package.json # 依赖: b@2.0.0
└── .bin/

由于依赖提升,所以b包版本1.0.0(先遇到)于是,被提升到顶层

npm 会尽量将依赖提升到顶层,但同一包的不同版本只能提升一个,其余版本会嵌套。

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

  1. 【转】包管理器Bower详细讲解

      包管理器Bower   今天自己用Angular写东西的时候,下载了Angular-seed项目,发现需要用到bower,之前也使用过,没有仔细了解,今天趁机了解到一些. bower的官网地址:  ...

  2. 包管理器Bower使用手冊之中的一个

    包管理器Bower使用手冊之中的一个 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.Bower介绍 Bower是一个适合Web应用的包管理器,它擅长 ...

  3. 包管理器Bower使用手册之一

    包管理器Bower使用手册之一 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.Bower介绍 Bower是一个适合Web应用的包管理器,它擅长前端的 ...

  4. NET Core 静态文件及JS包管理器(npm, Bower)的使用

    NET Core 静态文件及JS包管理器(npm, Bower)的使用 文章目录 在 ASP.NET Core 中添加静态文件 使用npm管理JavaScript包 使用Bower管理JavaScri ...

  5. Node.js包管理器:

    Node.js包管理器: 当我们要把某个包作为工程运行的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装 使用全局模式安装的包并不能直接在JavaScript文件中用require ...

  6. node包管理器

    NPM小结   nodejs的出现,可以算是前端里程碑式的一个事件,它让前端攻城狮们摆脱了浏览器的束缚,踏上了一个更加宽广的舞台.前端的可能性,从此更加具有想象空间. 随着一系列基于nodes的应用/ ...

  7. 【转】npm包管理器那些事

    原文链接:http://www.cnblogs.com/shuoer/p/7782125.html npm包管理器那些事! 今天和朋友针对npm包全局安装和本地项目安装这个梗展开的激烈的讨论,故此做一 ...

  8. Node.js_简介及其 npm 包管理器基本使用_npm_cnpm_yarn_cyarn

    Node.js 既是语言也是平台,跳过了 Apache.Nginx 等 HTTP 服务器,直接面向前端开发 JavaScript 是由 ECMAScript.文档对象模型(DOM)和浏览器对象模型(B ...

  9. nodejs的包管理器npm和cnpm

    http://www.ydcss.com/archives/18 3.npm介绍 3.1.说明:npm(node package manager)nodejs的包管理器,用于node插件管理(包括安装 ...

  10. npm --- Node.js包管理器

    目录 1. 安装Node.js 2. 运行npm 3. npm介绍 3.1 安装插件 3.2 更新插件 3.3 卸载插件 3.4 查看当前目录中的插件列表 4. 使用cnpm 4.1 安装 npm( ...

随机推荐

  1. Java API 之集合

    1. 包装类 (基本类型中没有多少我们能够使用的方法和属性,为了便捷我们需要自己去写) 针对每一种基本类型提供了对应的类的形式 --- 包装类   byte short int long float ...

  2. w3cschool-spring详解

    参考地址 https://www.w3cschool.cn/wkspring/dcu91icn.html Spring 体系结构 2021-11-03 18:18 更新 体系结构 Spring 有可能 ...

  3. 深入探讨数据库索引类型:B-tree、Hash、GIN与GiST的对比与应用

    title: 深入探讨数据库索引类型:B-tree.Hash.GIN与GiST的对比与应用 date: 2025/1/26 updated: 2025/1/26 author: cmdragon ex ...

  4. Oracle用户的创建和授权

    1 --创建用户.密码 2 create user infouser identified by "User@2022!"; 3 --授权连接数据库权限 4 grant conne ...

  5. Java中的Scanner、BufferedReader 和 StreamTokenizer

    1. Scanner 的使用与分析 简介: Scanner 是 Java 中一个用于解析原始类型(如 int.double 等)和字符串的类.它通常从输入流中逐个读取数据并进行解析,支持多种分隔符的使 ...

  6. 基于Java语言的开源能管平台才是最适合国内的能源管理平台

    在"双碳"战略背景下,能源管理已成为政府.企业实现可持续发展的必经之路.面对市场上各类能源管理平台,为何基于Java语言的开源解决方案才是最佳选择?本文将为您揭晓答案,并向您推荐我 ...

  7. 十二. Redis 集群操作配置(超详细配图,配截图详细说明)

    十二. Redis 集群操作配置(超详细配图,配截图详细说明) @ 目录 十二. Redis 集群操作配置(超详细配图,配截图详细说明) 1. 为什么需要集群-高可用性 2. 集群概述(及其搭建) 3 ...

  8. Shell脚本常用写法

    一.变量定义 | 赋值 | 输出 1.debugmap #!/bin/bash source /etc/profile # hive_json_tuple_params_orignal.tmp # e ...

  9. 最长不降子序列 n log n 方案输出与 Dilworth 定理 - 动态规划模板

    朴素算法 不必多说,\(O(n^2)\) 的暴力 dp 转移. 优化算法 时间为 \(O(n \log n)\) ,本质是贪心,不是 dp . 思路是维护一个单调栈(手写版),使这个栈单调不降. 当该 ...

  10. RabbitMQ(六)——路由模式

    RabbitMQ系列 RabbitMQ(一)--简介 RabbitMQ(二)--模式类型 RabbitMQ(三)--简单模式 RabbitMQ(四)--工作队列模式 RabbitMQ(五)--发布订阅 ...