阅读提要

首屏加载 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。这也是为什么第一步一定要先看可视化图。

收尾

包体积优化没有技巧,只有顺序:先测量,再拆分,然后剔除,最后守住。

跳过第一步,你会优化错地方;跳过最后一步,三个月后回到原点。

你认为这篇文章怎么样?

还没有表情反馈

分享文章