前端性能预算与构建优化实战

性能预算就像财务预算:先定一个数字上限,超出了就报警。前端性能预算就是给首屏 JS 体积、图片总量、加载时间设定硬性指标,在 CI 里自动检查,超标就阻断发布。本文从性能指标设定讲起,深入构建产物分析与分包压缩策略,帮你建立一套可持续的性能管控流程。

前端性能预算配图:蓝色渐变背景上速度表、上升柱状图、礼盒压缩包与天平秤,扁平科技插画
性能预算:给首屏体积设一个硬上限,超标就阻断

一、什么是性能预算

性能预算(Performance Budget)是一组量化的指标,用来约束前端应用的资源体积和加载性能。常见的指标包括:

指标推荐预算说明
首屏 JS 体积(gzip 后)≤ 170KB关键渲染路径上的 JavaScript
首屏 CSS 体积≤ 50KB关键渲染路径上的 CSS
首页总资源体积≤ 1MB所有首屏加载的资源总和
LCP(最大内容绘制)≤ 2.5sCore Web Vitals 核心指标
图片总量(首屏)≤ 500KB首屏可见的图片资源

预算不是拍脑袋定的,要根据你的目标用户网络环境来设。如果主要服务国内 4G 用户,首屏 170KB gzip 是一个合理的起点。

关键原则:预算必须是可自动化检查的数字。"页面要快"不是预算,"首屏 JS ≤ 170KB"才是预算。在 CI 里跑构建,输出产物体积,超出预算就失败——这才是性能预算的闭环。

二、用 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 里自动守护预算。记住:没有自动化检查的性能预算等于没有预算。