2026年2月,开发者daivuk发布了一款可运行的类Quake第一人称射击游戏,整个Windows可执行文件仅64KB。多关卡、四种敌人类型、纹理、音乐——全部塞进这个体积。秘诀不是魔法:他编写了自定义语言和虚拟机,因为标准工具链携带了大量未使用的功能。再多2KB的通用运行时,第四关就塞不进去了。
这个故事让我思考了很久。因为我打开的几乎每个Web应用都是QUOD的30到60倍。你现在阅读的页面,在Dev.to加载完成后,重量相当于400份QUOD同时运行。你应用所用框架的营销页面,比QUOD重三个数量级。我们集体遗忘了字节的价值。
这篇文章是我审计Next.js或Vite项目的完整手册——当Lighthouse分数飘黄时,我按这九个步骤执行,附带具体命令、预期输出和典型收益。一个下午砍掉50%到90%的包体积。没有"用Rust重写"的表演,只有删除。
基准测量
你无法改进未测量的东西。动手之前,先拿到真实数字。
Next.js项目运行npx next build,查看底部的"First Load JS"表格;Vite项目运行npx vite build,查看dist/输出大小。记录"First Load JS shared by all"和最大单个路由的数字。这个数字是后续审计的 accountability——如果第9步后没有下降至少30%,要么你漏了步骤,要么项目本身很小(那也很好,可以收工了)。
HTTP Archive 2025年度报告显示,桌面端JavaScript传输量中位数为612KB,移动端555KB。你的数字明显高于这个,说明有低垂的果实;明显低于,则已领先行业。
可视化包结构
文件列表不是地图,你需要地图。
Next.js安装@next/bundle-analyzer,在next.config.js中包装配置,然后ANALYZE=true npm run build;Vite安装rollup-plugin-visualizer,加入vite.config.ts,运行npx vite build。
分析器会在浏览器打开树状图。这是整个审计的单一真相源。每个大块都是一个问号,每个问号对应后面七步之一。花十分钟,悬停矩形,找出陌生的那些——你无法解释的,就是字节脂肪最多的。
删除日期库
整个JavaScript生态中最常见的包膨胀来源。Moment.js压缩前67KB,day.js仅7KB,date-fns按需导入可以更小。检查你的树状图:如果看到moment、luxon或完整的date-fns,这就是第一步要砍的。
典型收益:40-60KB。风险:零。现代浏览器内置的Intl.DateTimeFormat已经能处理80%的用例。
审计Lodash导入
import _ from 'lodash'会拉入完整库(70KB+)。改为import debounce from 'lodash/debounce',或使用lodash-es配合tree-shaking。更好的选择:大多数函数现代JavaScript已原生支持——debounce用AbortController,deep clone用structuredClone,chunk用Array.from。
典型收益:50-65KB。检查树状图中是否有完整的lodash块。
清理polyfill
你的目标浏览器是什么?如果package.json里的browserslist包含"last 2 versions"和"not dead",但你的分析器里还有core-js或regenerator-runtime的大块,说明配置与目标不匹配。
检查@babel/preset-env或@vitejs/plugin-legacy的配置。如果支持Chrome 90+,你可能根本不需要polyfill。典型收益:30-100KB,取决于你误伤了多少现代浏览器。
拆分路由
Next.js的动态导入const HeavyChart = dynamic(() => import('./HeavyChart'));Vite的import('./heavy-module.js')。关键:不要把动态导入当成万能药。如果每个路由都加载了相同的重型依赖,拆分路由只是延迟了痛苦。
先完成第3-5步,再执行这步。典型收益:取决于路由数量,常见的是首屏JS减少40-70%。
图片和字体优化
树状图不显示图片,但Lighthouse会。检查:是否在用next/image或vite-imagetools?是否还在用png/jpg而不是WebP/AVIF?字体方面,是否加载了完整的500KB可变字体,而只用了Regular和Bold两个字重?
典型收益:图片格式切换减少70-90%体积,字体子集化减少80-95%。
依赖审计
运行npm ls或pnpm why [package]。问自己:这个依赖做了什么事,我自己写需要多少行?有些包是合理的(zod、tanstack-query),有些是重复的(三个不同的HTTP客户端),有些纯属历史遗留(那个2019年为了解决一个已修复的bug而添加的包)。
典型收益:高度可变。我见过删除一个未使用的图表库直接减掉200KB。
最终验证
重新运行第1步的测量命令。对比数字。如果首屏JS没有下降至少30%,回到树状图,找最大的陌生矩形,重复第3-8步。
最后:在真实网络条件下测试。Chrome DevTools的"Slow 3G"预设,或WebPageTest的Cable/3G配置。包体积数字是代理指标,真实用户体验才是目标。
QUOD的64KB不是怀旧,是基准。我们不需要回到那个时代,但需要记住:每个字节都有成本,只是有人替我们付了。
热门跟贴