1. 依赖膨胀的根源

说实话,我见过太多团队被这个问题折磨了。

一个简单的 React 项目,装了几个依赖,node_modules 文件夹轻松突破 300MB。你明明只写了 200 行业务代码,结果项目体积比一个操作系统还大。这合理吗?

咱们今天就从 npm/node_modules 的“黑洞”现象讲起,把依赖膨胀的三大根源彻底说清楚。

1.1 黑洞现象:node_modules 到底装了什么?

先看一个真实案例。我去年接手过一个老项目,node_modules 文件夹居然有 1.2GB。项目本身代码不到 500KB。你想想看,这中间差了 2000 多倍。

为什么会这样?

我习惯用 npm ls --depth=0 先看一眼顶层依赖。结果发现,package.json 里只写了 12 个依赖。但实际安装的包,有 800 多个。

嗯,这就是典型的“黑洞”现象。你只装了一个包,它却给你拉进来几十个“亲戚”。

核心事实:一个 npm 包的平均传递依赖数量是 79 个。也就是说,你每装一个包,实际上会引入大约 80 个包。

1.2 根源一:传递依赖——你请了一个人,他带了一大家子

说白了,这就是依赖的“人传人”现象。

你装了一个 lodash,它依赖 lodash._baseclone,这个又依赖 lodash._stack…… 一层套一层。

我举个例子:

// 你写的 package.json
{
  "dependencies": {
    "express": "^4.18.0"
  }
}

// 实际安装后,express 带来了这些传递依赖
// express → body-parser → raw-body → bytes
// express → accepts → mime-types → mime-db
// express → send → destroy → ms
// ... 总共 30+ 个传递依赖

我在项目中遇到过最夸张的一次:一个 is-odd 包(判断奇数的工具),它依赖了 is-number,而 is-number 又依赖了 kind-of…… 就为了判断一个数字是不是奇数,拉进来 5 个包。

我的建议:装包之前,先跑一下 npm info <包名> dependencies,看看它到底会带多少“亲戚”进来。如果传递依赖超过 20 个,我一般会考虑自己写。

1.3 根源二:版本锁定——你以为锁住了,其实没有

很多团队用 package-lock.json 或 yarn.lock 来锁定版本。但这里有个坑。

你锁住的只是顶层依赖的版本。传递依赖的版本,依然可能被“升级”。

举个例子:

// 你的 package.json
{
  "dependencies": {
    "A": "1.0.0"
  }
}

// A 的 package.json 里写的是
{
  "dependencies": {
    "B": "^1.0.0"
  }
}

// 第一次安装:A@1.0.0 + B@1.0.0
// 三个月后重新安装:A@1.0.0 + B@1.2.0(因为 B 发布了新版本)

你看,A 的版本没变,但 B 被悄悄升级了。如果 B@1.2.0 里多了一个新的传递依赖,你的 node_modules 就又多了一堆东西。

我曾经踩过的坑:有一次线上构建,同样的代码,两次构建出来的 node_modules 大小差了 50MB。查了半天,发现是一个底层工具库的传递依赖被“悄悄”升级了。从那以后,我强制团队把 package-lock.json 也纳入代码审查。

1.4 根源三:重复打包——同一个包,装了两遍

这是最让人头疼的问题。你想想看,如果 A 依赖了 lodash@4.0.0,B 依赖了 lodash@4.1.0,npm 会怎么做?

它会装两个版本的 lodash!

node_modules/
├── A/
│   └── node_modules/
│       └── lodash@4.0.0/   ← 版本 1
├── B/
│   └── node_modules/
│       └── lodash@4.1.0/   ← 版本 2
└── lodash@4.1.0/           ← 版本 3(被提升到顶层)

同一个 lodash,在 node_modules 里出现了 3 次。每个版本都占 5MB,这就白白浪费了 10MB 空间。

我见过最离谱的项目,chalk 这个包被装了 7 个不同的版本。就为了颜色输出,占了 35MB。

数据说话:根据 npm 官方统计,一个中型项目中,平均有 30% 的包存在重复安装。这些重复的包,占用了 node_modules 总大小的 40% 以上。

1.5 一张图看懂依赖膨胀

下面这张 SVG 图,把三大根源的关系画清楚了:

依赖膨胀的三大根源 node_modules 黑洞 传递依赖 你装一个包 它带来几十个“亲戚” 平均每个包带 79 个传递依赖 层层嵌套,指数级增长 版本锁定陷阱 lock 文件锁了顶层 但传递依赖还在“浮动” 同样的代码,不同时间安装 node_modules 大小可能不同 重复打包 同一个包,多个版本 同时存在于 node_modules 平均 30% 的包被重复安装 白白浪费 40% 的磁盘空间

1.6 怎么判断你的项目有没有“膨胀”?

我一般用这几个命令快速诊断:

# 1. 看看 node_modules 到底多大
du -sh node_modules

# 2. 看看哪些包占空间最多
du -sh node_modules/* | sort -rh | head -20

# 3. 看看有没有重复的包
npm ls --depth=0 | grep "deduped"

# 4. 看看传递依赖的层级有多深
npm ls --depth=5 | head -50

如果 node_modules 超过 500MB,或者重复包的比例超过 20%,嗯,你该动手优化了。

一个小技巧:我习惯用 npx cost-of-modules 这个工具,它能直观地展示每个包占了多少空间。一眼就能看出谁是“胖子”。

1.7 总结一下

依赖膨胀不是一天形成的。它是传递依赖、版本锁定陷阱、重复打包这三个问题长期积累的结果。

你想想看,一个项目开发两年,每个版本都往里加几个包,每个包又带几十个传递依赖…… 项目不膨胀才怪。

下一节,咱们会讲怎么用工具把这些“胖子”揪出来。但今天先把根源搞清楚。只有知道病根在哪,才能对症下药。

可可精品资料库