好家伙

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. 用 C# 实现检测系统环境变量 “Path” 中是否有某个值,没有就添加的方法

    用 C# 实现检测系统环境变量 "Path" 中是否有某个值,没有就添加的方法: using System; using System.Collections.Generic; u ...

  2. 隐私集合求交(PSI)-多方

    本文主要讲解一个多方的PSI协议,文章转载:隐私计算关键技术:多方隐私集合求交(PSI)从原理到实现以及多方隐私求交--基于OPPRF的MULTI-PARTY PSI:原论文:Practical Mu ...

  3. 创建多线程的方式二:实现Runnable接口

      /** * 创建多线程的方式二:实现Runnable接口 * 1. 创建一个实现了Runnable接口的类 * 2. 实现类去实现Runnable中的抽象方法:run() * 3. 创建实现类的对 ...

  4. UTS Open '21 P7 - April Fools

    传送门 前言 本题是笔者keysky与同学yangbaich讨论+推式子一整个晚上以及讨论前ybc的一整个下午做出来的,综合起来是 \(34\) 个转移方程,对于整道题来说,贡献大抵为我 \(2\) ...

  5. C#中多线程实现后台任务的三种方式

    第一种:通过Invoke()在应用程序的主线程上执行指定的委托 //新开一个线程,显示当前时间 new Thread(o => { while (true) { Thread.Sleep(100 ...

  6. 【T+重要提示】日期不在业务期间范围内

    2021年就来了,今天是新年的第二个工作日.相信很多会计朋友们在打开T+软件的时候,会看到这么个提示框: 用友T+2020年没有做完账务,提前建立2021年度帐的步骤 (1)用账套主管选择2020年1 ...

  7. 基于生成式AI的访问控制, 迁移传统安全策略到基于LLM的风险分类器

    基于规则的风险分类 风险分类是网络安全系统的核心能力之一,它将访问请求和命令映射到其风险级别/类别:高(High).中(Medium).低(Low).目前,即便是在大规模环境中,风险分类器仍主要采用基 ...

  8. AI工具推荐——open-interpreter

    前言 Open Interpreter 是一个能让大型语言模型在你本地电脑上运行代码的工具. 简单来说: 它提供了一个类似于 ChatGPT 的自然语言界面,让你能通过代码与电脑互动. 你可以用它来: ...

  9. 深入剖析Base64加解密中遇到的坑点

    前言 最近开发过程中遇到了关于使用base64加密传输遇到的神奇问题.需求就是用户的id在链接上露出时需要加密处理,于是后端把下发的用户id改成了base64加密处理后下发了,前端只需要把加密后的用户 ...

  10. Mybatis 返回自增主键的id

    Mybatis 返回自增主键的idkeyProperty=id:封装到对象中的id字段当中keyColumn=id:封装到数据库的id这一列order=AFTER:在新增语句之后执行 方法一 < ...