前端性能预算与构建优化实战
性能预算就像财务预算:先定一个数字上限,超出了就报警。前端性能预算就是给首屏 JS 体积、图片总量、加载时间设定硬性指标,在 CI 里自动检查,超标就阻断发布。本文从性能指标设定讲起,深入构建产物分析与分包压缩策略,帮你建立一套可持续的性能管控流程。
一、什么是性能预算
性能预算(Performance Budget)是一组量化的指标,用来约束前端应用的资源体积和加载性能。常见的指标包括:
| 指标 | 推荐预算 | 说明 |
|---|---|---|
| 首屏 JS 体积(gzip 后) | ≤ 170KB | 关键渲染路径上的 JavaScript |
| 首屏 CSS 体积 | ≤ 50KB | 关键渲染路径上的 CSS |
| 首页总资源体积 | ≤ 1MB | 所有首屏加载的资源总和 |
| LCP(最大内容绘制) | ≤ 2.5s | Core Web Vitals 核心指标 |
| 图片总量(首屏) | ≤ 500KB | 首屏可见的图片资源 |
预算不是拍脑袋定的,要根据你的目标用户网络环境来设。如果主要服务国内 4G 用户,首屏 170KB gzip 是一个合理的起点。
二、用 Vite 分析构建产物
Vite 构建完成后默认只输出一个简单的体积表格。要深入分析哪个库占了多少体积,可以用 rollup-plugin-visualizer:
npm install --save-dev rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
}),
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ["react", "react-dom"],
router: ["react-router-dom"],
utils: ["lodash-es", "dayjs"],
},
},
},
},
});
构建后会自动打开一个交互式 treemap,直观展示每个依赖的体积占比。你会经常发现某个 100KB 的库只用了一个函数——这时候就该考虑换轻量替代或按需引入。
三、分包策略:手动 Chunk 拆分
把所有代码打成一个 bundle 会有两个问题:每次发版用户都要重新下载全部代码、公共库和业务代码混在一起不利于缓存。分包的核心思路是:把不常变动的第三方库单独打一个 chunk,业务代码变动时用户只需要重新下载小体积的业务 chunk。
// 推荐的分包策略
manualChunks(id) {
if (id.includes("node_modules")) {
// React 生态单独分包
if (id.includes("react") || id.includes("scheduler")) {
return "vendor-react";
}
// 工具库单独分包
if (id.includes("lodash") || id.includes("dayjs")) {
return "vendor-utils";
}
// 其他第三方库
return "vendor";
}
}
效果:第三方库 chunk 文件名带内容 hash,只要库版本不变,hash 就不变,浏览器缓存就能命中。业务代码 chunk 每次发版都变,但体积小,下载快。
四、压缩与传输优化
Vite 默认开启了 ESBuild 压缩,但还有几个进阶选项值得关注:
build: {
minify: "esbuild", // 比 terser 快 20-40 倍,压缩率接近
cssMinify: true, // CSS 也压缩
sourcemap: false, // 生产环境关掉 sourcemap
assetsInlineLimit: 4096, // 小于 4KB 的资源内联成 base64
chunkSizeWarningLimit: 500, // chunk 超过 500KB 时警告
rollupOptions: {
output: {
compact: true,
hashCharacters: "base36",
},
},
}
同时在服务器上开启 gzip 或 brotli 压缩。brotli 的压缩率比 gzip 高 15-20%,现代浏览器都支持。Nginx 配置示例:
brotli on;
brotli_types text/css application/javascript application/json image/svg+xml;
brotli_comp_level 6;
gzip on;
gzip_types text/css application/javascript application/json;
五、图片优化与懒加载
图片通常是首屏体积最大的资源。三个优化手段:用 WebP 格式(比 JPEG 小 30%)、响应式图片(srcset)、懒加载。
<!-- 响应式图片 + 懒加载 -->
<img
src="/images/hero-small.webp"
srcset="/images/hero-small.webp 480w,
/images/hero-medium.webp 800w,
/images/hero-large.webp 1200w"
sizes="(max-width: 600px) 480px,
(max-width: 1000px) 800px,
1200px"
loading="lazy"
alt="首屏配图">
注意:首屏以上的图片不要加 loading="lazy",否则会延迟首屏渲染。懒加载只用于首屏以下的图片。
六、CI 自动化检查
把性能预算接入 CI,每次 PR 自动检查:
# .github/workflows/perf-budget.yml
# 构建后检查产物体积
- name: Build and check budget
run: |
npm run build
node scripts/check-budget.js
# scripts/check-budget.js
# 读取 dist/assets 下的文件大小
# 与预算对比,超出则 exit 1
这样任何一个 PR 试图把首屏 JS 从 170KB 涨到 200KB,CI 会直接失败,开发者必须优化后才能合并。
七、总结
性能预算不是一次性优化任务,而是一套持续的工程流程。从设定可量化的指标开始,用可视化工具找到体积大头,通过分包和压缩把体积降下来,最后在 CI 里自动守护预算。记住:没有自动化检查的性能预算等于没有预算。