惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Cloudflare Blog
T
Tenable Blog
V
Vulnerabilities – Threatpost
T
Troy Hunt's Blog
SecWiki News
SecWiki News
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Secure Thoughts
Cyberwarzone
Cyberwarzone
A
Arctic Wolf
H
Heimdal Security Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
N
News and Events Feed by Topic
The Hacker News
The Hacker News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Cisco Talos Blog
Cisco Talos Blog
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
Forbes - Security
Forbes - Security
Security Archives - TechRepublic
Security Archives - TechRepublic
Project Zero
Project Zero
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
T
Tor Project blog
WordPress大学
WordPress大学
AWS News Blog
AWS News Blog
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
Cloudbric
Cloudbric
O
OpenAI News
U
Unit 42
Google DeepMind News
Google DeepMind News
Simon Willison's Weblog
Simon Willison's Weblog
Recorded Future
Recorded Future
N
News | PayPal Newsroom
S
Schneier on Security
F
Full Disclosure
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
P
Privacy International News Feed
L
LINUX DO - 最新话题
F
Fortinet All Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Recent Announcements
Recent Announcements

kok的笔记本

macOS 里 神秘的 kernel_task ,为什么一直在写入硬盘? 产品 谁来救救我的Mac存储空间-清理工具横评:付费vs免费,从界面到命令行 2023 读书推荐 写给播客嘉宾的录制说明书 在 Electron 中使用 Vue Devtools 几何原本中勾股定理的证明 Mac 安装软件时提示 已损坏,无法打开 应该怎么办 PegBoard 你的数字白板 少数派读者与作者的 NewsLetter 闲棋冷子-2-2021年我读过哪些很棒的书 闲棋冷子-1-相信你的身体,它不会骗你 修复 macOS 中 Cisco Anyconnect 报的奇怪Bug 我的 NAS 使用记录 Omniplan 入门教程 Hugo升级0.74记录 一年级产品经理需要掌握什么知识? 无籍之谈-3-和盖盖聊聊尼尔·盖曼与中外科幻 露营笔记-装备篇 无籍之谈-2-和鹏仔聊华尔街兴盛史,给小白投资者的一些建议 介绍一下《无籍之谈》 无籍之谈-1-和不土聊佛学,谈谈《一心走路》《僧侣与哲学家》 高达模型入门 - 工具整理 5 分钟了解 Sketch 的新特性 Tint - 色调 Hugo 主题移动端开发的一些心得 修复 macbook 键盘连击问题 在 Hugo 中使用 Open Graph 提升分享效果 选小米10 还是小米10 Pro? 利用 Python 批量合并 Excel 文件 为什么家国梦不好玩? 高雄85大楼 Mac优秀软件推荐-Itsycal 找到并和正确使用无版权资源 Excel 常用函数 一 无线吸尘器挑选指南 钢铁、蒸汽与 19 世纪 聊聊《铁道之旅》 如何挑选一个电脑电源? 有用的正则表达式 给psd瘦身 2019第二季度读书总结 组装 ITX 黑苹果主机 Intel CPU 挑选指南 常用的 Markdown 工具 2019第一季度读书总结 怎么用尺规作图画75°角? 在 Hugo 中使用 Sass 真正的换位思考-《爱的沟通》读书笔记 Hugo 中使用思维导图 反叛公司中的治理者和顾问玩法 如何搭配成为一个盐系男生? 反叛公司 Rebel Inc. 入门指南 如何评估需求,预估开发成本? 小米8、小米8SE、小米8青春版参数对比 微信外部链接内容管理规范分析 在售 iPhone 参数对比 给Hugo blog中增加一些插件 微信 MacOS 多开的一些实践 矩阵与线性变换的概念理解 尺规作图小习题 用 hugo 和 netlify 搭建blog 介绍我自己 瓜摊老板 Really? EDM邮件营销,你需要知道 果子沟大桥 喀赞其的马车 清晨的木梨硡 hexo 主题开发 青龙寺的狮子 用一个特殊方法解决同位词问题 语义化 html 标签 数模的基本思路和写作攻略 hexo blog 搭建 0.1,0.1^0.1,为什么数值是波动的? 智能手机之旅 老师和学生的博弈 这些使用抗生素的方式,你做的对吗? 炼金术与钢之炼金术师 kok的笔记本 kok的笔记本 中国长征运载火箭家族-PegBoard
node_modules 为什么总是这么大:一次从原理到实践的瘦身记录
kokdemo · 2026-06-23 · via kok的笔记本

做前端做久了,多少都会被 node_modules 气到一次。

明明业务代码没多少,仓库一拉下来,磁盘先掉几百 MB。再来几个子项目,风扇开始转,空间开始红,最后你只能盯着 Finder 里那个熟悉的文件夹发呆。

我以前对这事的态度也很典型:知道它大,但懒得追。直到本地项目越来越多,机器空间越来越紧,我才认真拆了一次,想看看到底是谁在吃磁盘。

拆完之后,结论其实不复杂:

很多时候不是“代码大”,而是“开发环境大”。

真正占地方的,往往是 TypeScript、构建工具、本地运行时、图片处理库、测试环境这些东西。业务源码反而没你想的那么重。

也是因为这次排查,我后面基本把新项目都切到了 pnpm。它不是什么银弹,但在“本地有很多 Node 项目”这个场景里,确实比 npm 省心。

最表层的原因当然是依赖多,但这句话没什么帮助。更准确一点说,node_modules 容易膨胀,通常是几个因素叠在一起。

先是依赖树本来就深。你在 package.json 里看到的是几十个直接依赖,落到磁盘上往往已经变成一大片间接依赖。

装一个构建工具,背后一般会跟出来这些东西:

  • 转译器
  • polyfill
  • 文件系统工具
  • 日志工具
  • 路径处理工具
  • 颜色输出库
  • 各种平台兼容层

表面上你只装了一个包,实际上你是把一整串开发链一起搬回来了。

第二个问题更实际:不同项目会重复存同样的依赖。

如果你电脑里只有一个 Node 项目,这个问题还不算明显。可一旦项目变成 5 个、10 个,重复成本就会很夸张。

比如 reacttypescriptviteeslint 这种常见依赖,很多项目都会用。传统安装方式下,这些东西很容易在每个项目的 node_modules 里各放一份。

单看一个仓库好像没什么,放到整台机器上看,就很肉疼。

pnpm 官方文档讲得很直接:如果有 100 个项目都用同一个依赖,npm 可能会在磁盘上留下 100 份;pnpm 则会把包存到一个统一的内容寻址存储里,再通过链接复用。
来源:pnpm Motivation

第三个问题是版本不一致。

很多重复不是因为“你装太多了”,而是因为同一个包在不同地方解析成了不同版本。哪怕只差一点点,也没法完全复用。

比如:

  • A 依赖 foo@^1.0.0
  • B 依赖 foo@^1.1.0
  • C 依赖 foo@~1.1.2

看起来都是 foo,最后落下来的未必是同一个版本。于是磁盘上就得保留多份。

最后还有个经常被忽略的点:node_modules 不只是大,它还很碎。

目录多、小文件多,会让下面这些事情都变得更慢:

  • 文件系统元数据开销变大
  • 复制、删除、索引、杀毒扫描都会变慢

所以你感受到的往往不只是“占空间”,而是整个开发环境都显得很笨。

二、看一个真实例子:重的不是源码,而是开发环境

前面这些还是泛泛而谈。真正让我下决心换包管理器的,是我把手头项目拆开看了一次。

当时有三块体积特别显眼:

  • 主项目的 node_modules:262M
  • worker:169M
  • website:147M

第一眼确实有点吓人。但继续拆下去以后,事情反而变简单了:业务代码没那么重,真正重的是每个子项目背后的工具链。

1. 为什么 node_modules 会有 262M

这 262M 基本就是主扩展项目自己的前端构建环境。

占得比较多的有:

  • typescript:22M
  • happy-dom:16M
  • 多个版本的 esbuild:单个大约 9M
  • 多个版本的 vite / rollup / jsdom

这里还有个很容易看走眼的点。因为项目本身已经在用 pnpm,所以 node_modules 顶层很多包看起来像 0B。那不是真的没占空间,只是软链接。真正的内容在 node_modules/.pnpm/ 里。

所以这 262M 不是“代码写多了”,而是 TypeScript、构建器、DOM 模拟环境这些开发依赖在占地方。

2. 为什么 worker 会有 169M

worker 也差不多,大头几乎全在 worker/node_modules/.pnpm/。本质上是 Cloudflare Worker 这一套本地开发链很重。

主要是这些:

  • @cloudflare/workerd-darwin-arm64:83M
  • typescript:23M
  • @img/sharp-libvips-darwin-arm64:15M
  • @cloudflare/workers-types:10M
  • wrangler:6.8M
  • workerd:4.2M
  • miniflare:3.0M

换句话说,重的不是业务逻辑,而是:

  • Cloudflare 本地运行时
  • 类型定义包
  • 图片处理库
  • 本地调试与编译工具

3. 为什么 website 会有 147M

website 也是同样的路数。主要体积都在 website/node_modules/.pnpm/,背后是 Astro 那套静态站点构建链。

比较显眼的几项:

  • typescript:23M
  • @img/sharp-libvips-darwin-arm64:15M
  • 两个版本的 esbuild:各 9-10M
  • @shikijs/langs:9.8M
  • astro:5.4M
  • @astrojs/compiler:5.1M

再加上 zodshikiprismjs 这些,单个看着还行,堆起来就不小了。

所以这 147M 主要还是构建、Markdown 解析、代码高亮相关的依赖,不是网站源码本身。对应的 website/src,当时其实只有 168K。

4. 这组数据说明了什么

这次排查最有意思的地方就在这儿:

  • node_modules 262M:主扩展构建链
  • worker 169M:Cloudflare Worker 本地开发链
  • website 147M:Astro 官网构建链

真正小的是源码,真正重的是开发环境。

所以很多时候你去折腾业务代码体积,对本地磁盘没什么帮助。空间压力常常不在 src/,而在 TypeScript、构建器、运行时、测试环境、代码高亮、图片处理这些配套工具上。

三、为什么传统办法治标不治本

碰到 node_modules 太大,最常见的反应一般有三种。

第一种,删了重装。

最常见的命令大概是这样:

rm -rf node_modules package-lock.json
npm install

这招有时能清掉一点历史残留,但本质上只是重建,不是瘦身。如果依赖结构没变,装回来还是一样大。

第二种,跑 npm dedupe

npm dedupe 会尝试把依赖往上提,减少重复项。npm 官方文档的描述是:它会搜索本地依赖树,并尝试简化整体结构,让多个依赖更有效地共享同一个包。
来源:npm dedupe 文档

这当然不是没用,但边界也很明显:

  • 它只能在当前项目内部尽量去重
  • 它不能解决“多个项目之间重复存储”的问题
  • 遇到版本不兼容时,也没法强行合并

所以它更像收拾房间,不是搬家。

第三种,告诉自己以后少装点包。

方向没错,但实际很难靠意志力解决。

因为现在很多体积不是你主动选出来的,而是构建工具、测试工具、代码规范工具一路带出来的。你当然可以克制一点,但光靠“少装几个包”很难真正见效。

四、pnpm 为什么能明显省空间

我后来切到 pnpm,最有价值的地方其实不是“更快”,而是它换了一种存储方式。

简单说,pnpm 不会让每个项目都老老实实拷一整份依赖。

它用的是 content-addressable store,也就是内容寻址存储。可以粗暴理解成这样:

  • 同样内容的包,只在磁盘上保存一次
  • 各个项目安装时,不再复制整份文件
  • 而是通过硬链接、符号链接把它们组织成可用的 node_modules

pnpm 官方文档也专门提到这一点:同版本依赖可以跨项目共享,哪怕版本更新,也只需要为真正变化的文件增加存储。
来源:pnpm Motivation

它还有一个容易让人误会的地方:目录看起来有点怪。

很多人第一次看 pnpmnode_modules 都会有点愣,因为里面会多出一个 .pnpm 目录,再通过一层层链接把依赖串起来。

官方文档的解释其实挺清楚:

  • node_modules 中真正的文件会硬链接到全局 store
  • 再通过符号链接搭出依赖关系
  • 这样既能节省空间,也能兼容 Node.js 的模块解析机制
    来源:pnpm Symlinked node_modules structure

所以它不是把依赖变没了,而是少做了很多没必要的重复拷贝。

还有一点我自己挺喜欢:它更严格。

传统扁平化 node_modules 有个老问题:有些包明明没写进 package.json,但因为被提升到根目录,项目里居然也能跑。

这种隐式依赖平时不一定出事,一换环境就容易炸。pnpm 默认更严格,通常会更早把这类问题暴露出来。官方文档也提到,非扁平结构的一个好处就是,只有真实依赖图里的包才可访问。
来源:pnpm Symlinked node_modules structure

五、我在实践里是怎么切到 pnpm

我切 pnpm 的目标挺朴素,不是为了跑分,就是想解决两个现实问题:

  • 本地有多个 Node 项目,重复依赖占空间明显
  • 删除和重装 node_modules 的成本越来越高

最后我的做法也很简单:新项目直接用 pnpm,老项目慢慢迁。

安装方式不复杂。

如果 Node 版本比较新,我更建议直接用 Corepack:

corepack enable
corepack prepare pnpm@latest --activate

当然也可以全局装:

已有项目切过去,一般这样就够了:

rm -rf node_modules package-lock.json
pnpm install

如果仓库之前用的是 Yarn,那就把 yarn.lock 也清掉。一个仓库最好只留一种包管理器。

我后来顺手把团队里的习惯也统一了:

  • 提交 pnpm-lock.yaml
  • README 里的安装命令改成 pnpm install
  • CI 改成 pnpm install --frozen-lockfile

真正的体感变化,其实不在某一个项目上。

切过去之后,最明显的不是某个项目突然小了 90%,而是整台机器上的 Node 项目没那么臃肿了。感受大概来自这几件事:

  • 多项目之间重复依赖明显减少
  • 重装依赖更快
  • 清理和迁移项目时心理负担更小

尤其是你手上同时有这些东西的时候:

  • 主业务项目
  • 管理后台
  • 实验项目
  • 脚手架
  • 若干 demo

这时候 pnpm 的优势会比单仓库明显很多。

另外,如果你只是想临时回收一点空间,最有效的办法通常也不是删源码,而是删暂时不用的构建产物和依赖目录。

比如:

  • extension/dist
  • 暂时不用哪个子项目,就先删它的 node_modules

还是拿前面的例子说:

  • worker/node_modules 删掉,往往就能直接回收接近 169M
  • website/node_modules 再删掉,又能回收接近 145M

我后来处理磁盘紧张时,基本就是这个思路:不是到处找哪段代码最大,而是先看接下来几天不会碰哪个子项目,然后优先删它的依赖环境。

六、迁移时要注意的坑

pnpm 当然也不是完全没有代价。

第一个坑,是老项目可能会依赖一些本来就不该存在的宽松行为。

有些项目以前在 npm 下能跑,不代表它真的写对了。切到 pnpm 以后,如果某个包没显式声明却偷偷在用,往往就会直接报错。

这事烦归烦,但从长期看不算坏事。只是历史债终于被翻出来了。

第二个坑,是少数工具对符号链接支持一般。

现在主流工具链对 pnpm 基本都挺友好,但少数老工具、老脚本、老插件,还是会默认 node_modules 是传统扁平结构。

如果真遇到兼容性问题,pnpm 也不是没留后路。比如可以把 nodeLinker 设成 hoisted,尽量靠近传统结构。官方文档也明确提到,如果工具链对 symlink 支持不好,可以用这种模式。
来源:pnpm Motivation

所以它不是那种“你要么全信,要么别用”的方案。

第三个坑其实是协作问题。

如果团队里有人跑 npm install,有人跑 pnpm install,锁文件和依赖树很容易来回漂。最省心的办法还是统一:

  • 一个仓库只保留一种锁文件
  • CI 只认一种包管理器
  • 文档、脚本、提交规范全部统一

七、什么时候我会优先推荐 pnpm

如果你符合下面这些情况,我基本都会建议试试:

  • 电脑里有很多 Node 项目
  • 在做 monorepo
  • 经常删装依赖
  • 磁盘空间比较紧张
  • 希望依赖关系更严格、更可控

反过来说,如果你只有一个很小的项目,团队工具链也已经完全绑在 npm 或旧版 Yarn 上,那这件事可以先往后放。

八、我的结论

回头看,node_modules 占空间这件事,问题往往不在“你写了多少代码”,而在“你养了一整套多重的开发环境”。

删缓存、重装、少装包,这些办法不是没用,只是都偏局部。真正让我体感明显改善的,还是 pnpm 这种从存储模型上减少重复的方案。

它最打动我的地方,不是一句“更快”,而是三件更实际的事:

  • 多项目之间真的能复用依赖
  • 依赖关系更清楚,隐式问题更早暴露
  • 对 monorepo 或多仓库开发很友好

如果你现在也正被 node_modules 折磨,我会建议别只想着“怎么清掉它”,也顺手想一下:是不是该把包管理器一起换了。

参考资料