阅读提要
首屏加载 4.8MB,用户等六秒。三次瘦身把体积压到 800KB:分析、拆分、剔除、守住。每一步都有具体的工具和判断标准。
先量化,别猜
优化包体积的第一步不是删代码,是搞清楚体积构成。
npx vite-bundle-visualizer
# 或 webpack
npx webpack-bundle-analyzer dist/stats.json
它会生成一张方块图,面积代表体积。看一眼通常就能发现两三个「巨无霸」——往往是某个 UI 库的全量引入,或者一个用不上的图表库。
没有这一步就动手,最常见的结局是:花两天手动优化掉了 30KB,而某个依赖还占着 600KB。

第一次:拆分与懒加载
路由级拆分是最容易拿到的收益。多数框架默认支持,但要确认真的生效:
const routes = [
{ path: '/', component: () => import('./pages/Home.vue') },
{ path: '/admin', component: () => import('./pages/Admin.vue') }
]
用 () => import() 而不是直接 import,后者会打进主包。
重组件按需加载。编辑器、图表、地图这类体积大且不一定用到的,用 defineAsyncComponent 或动态 import():
const Editor = defineAsyncComponent(() => import('./RichEditor.vue'))
判断标准:首屏不需要它,就不该在主包里。
这一步通常能减掉 40%-60%。收益大、风险低,应该先做。
第二次:剔除重复与全量
重复依赖是隐形杀手。两个库各自依赖了不同版本的 lodash,打包时会有两份:
pnpm why lodash
如果出现多版本,用 resolutions(npm)、overrides(pnpm)强制统一。
全量引入更常见:
// 把整个组件库打进来
import { Button, Table, Modal } from 'ui-library'
// 如果库不支持 tree-shaking,等于全量
检查方法:看可视化图里这个库占多少。如果只是用了三个组件却占了 500KB,考虑按需引入插件,或者换成支持 ESM 的替代品。
polyfill 也常被忽略。为兼容 IE 引入的一堆 polyfill,在现代浏览器里完全是浪费。用 browserslist 明确目标,构建工具会自动裁剪。
第三次:压缩与传输
代码压缩(minify)是默认行为,但要确认没被关掉。
传输压缩是另一回事:
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
# 更好的选择
brotli on;
brotli_types text/css application/javascript application/json image/svg+xml;
Brotli 比 gzip 小 15%-25%,现代浏览器都支持。代价是压缩更慢,所以静态资源预压缩、动态响应看情况。
注意:如果 CDN 已经压缩,源站不要重复压缩;如果源站压缩了但没设 Vary: Accept-Encoding,缓存会串味。
守住:CI 里的体积预算
前三步做完,体积会降下来。但三个月后它会涨回去——这是必然的,因为每次加功能都只增加一点点。
唯一的解法是把体积变成一条会失败的检查:
{
"bundlesize": [
{ "path": "dist/assets/*.js", "maxSize": "250 kB" },
{ "path": "dist/assets/*.css", "maxSize": "60 kB" }
]
}
超过阈值,CI 红。开发者要么优化,要么显式调高阈值并在 PR 里说明理由。
后一种情况才是关键:让「变大」变成一个需要解释的动作,而不是悄悄发生。
几个容易忽略的点
字体文件。一个中文字体 5MB,比整个 JS 还大。必须子集化,并且 font-display: swap。
图片。首屏大图用 loading="eager" + fetchpriority="high",其余 loading="lazy"。格式用 WebP/AVIF,<picture> 做降级。
第三方脚本。统计、客服、A/B 测试的 SDK 经常比自己的代码还大。用 async/defer,或者延后到交互后再加载。
CSS。Tailwind 这类原子化方案如果配置不当,会把所有用不到的类都打进去。确认 content 配置覆盖了所有模板文件。
一个真实的数字
某站点优化前:首屏 JS 4.8MB(gzip 后 1.2MB),FCP 6.2s。
三轮之后:
- 路由拆分:4.8MB → 2.1MB;
- 剔除重复的 moment + 全量引入的图表库:2.1MB → 1.1MB;
- Brotli + 字体子集化:1.1MB → 800KB(传输 280KB)。
FCP 从 6.2s 降到 1.8s。
其中最大的单项是那个只用了一个折线图却全量引入的图表库——620KB。这也是为什么第一步一定要先看可视化图。
收尾
包体积优化没有技巧,只有顺序:先测量,再拆分,然后剔除,最后守住。
跳过第一步,你会优化错地方;跳过最后一步,三个月后回到原点。
你认为这篇文章怎么样?
还没有表情反馈

一起聊聊
一个想法、一点补充,或一句简单的问候。
评论与回复
友善交流,彼此尊重第一句话,留给你。
读完有什么感受?欢迎成为第一个分享的人。