Skip to content
返回质量保障

包体积

保持 JavaScript 和 CSS 包小使页面快加载,尤其移动和慢网络。

为什么

每字节 JavaScript 须下载、解析、编译、执行后页面才可交互——慢 CPU 移动设备上比桌面慢 10 倍。500KB JavaScript 包在中端手机上解析/编译 2.5 秒,这还是运行前。包体积非审美关注;是 1 秒加载和 5 秒加载的差,用户弃慢页。测和控制包体积的工具成熟(webpack-bundle-analyzer、size-limit、import-cost),减它的技术众所周知(代码分割、摇树、动态导入)。纪律是追踪体积把每 KB 当预算资源。

何时使用

每个 PR 追踪包体积(size-limit 或 bundlesize)。设预算阻超预算 PR。体积意外增长时用 webpack-bundle-analyzer 审计包。

如何做

1. 测当前包体积和组成
   - webpack-bundle-analyzer 或 Rollup 可视化器: 显每模块及其体积
   - 找大依赖、重复导入和不应在的模块
   - 追 gzip 体积(用户下载的)非仅原始

2. 设体积预算 CI 中强制
   - size-limit 或 bundlesize: 包超预算即构建失败
   - 每入口预算: 主包 < 200KB、每路由块 < 50KB
   - 允许带注释解释的小增;阻静默增长

3. 应用代码分割减初始包
   - 路由分割: 每路由单独块,导航时加载
   - 组件分割: 重组件(图表、编辑器)按需加载
   - 首屏不需要的一切用 React.lazy / dynamic import()

4. 消不必要依赖
   - 审导入: date-fns(20KB)能做时还需 moment.js(300KB)吗?
   - 查重复依赖(树中同包两版本)
   - 用 import-cost 作 VS Code 扩展打字时见每导入体积

5. 启摇树和生产优化
   - 可摇包 package.json 中 sideEffects: false
   - 用生产模式(NODE_ENV=production)启压缩和死代码消除
   - 目标现代浏览器(esbuild target: es2020)跳旧浏览器 polyfill
   - CDN 级服务 Brotli 或 gzip 压缩

示例

// package.json——size-limit 预算
{
  "size-limit": [
    { "path": "dist/main.js", "limit": "200 KB", "gzip": true },
    { "path": "dist/vendor.js", "limit": "150 KB", "gzip": true },
    { "path": "dist/routes/dashboard.js", "limit": "50 KB", "gzip": true }
  ]
}

// CI: .github/workflows/size.yml
name: Bundle Size Check
on: [pull_request]
jobs:
  size:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: npx size-limit
        # 任一包超限即失败

// 代码分割: 路由级
import { lazy, Suspense } from "react";
const Dashboard = lazy(() => import("./routes/Dashboard")); // 单独块
const Settings = lazy(() => import("./routes/Settings"));   // 单独块

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// 用 date-fns(20KB、可摇)替 moment.js(300KB)
// 之前: import moment from "moment";  const formatted = moment(date).format("YYYY-MM-DD");
// 之后:
import { format } from "date-fns";
const formatted = format(date, "yyyy-MM-dd");

常见问题

  • ⚠只用一个函数时导入整个库(import _ from 'lodash')——打包器不能摇默认全模块导入,故 70KB lodash 为一个 debounce 发。
  • ⚠CI 无体积预算,包随每 PR 静默增长——200KB 一年变 500KB,无人注意,页面变慢无单变更可怪。
  • ⚠为不在目标受众的浏览器发 polyfill——应用是只在常青 Chrome 跑的 B2B 工具时包含 50KB IE11 polyfill。
  • ⚠vendor 和应用代码在单包——每次应用变更失效 vendor 缓存,用户每次部署重下 React(150KB)而非仅变更的应用代码。

最佳实践

  • ✓用 size-limit 设体积预算 CI 任违规即失败——不强制预算是建议,建议在截止压力下被无视。
  • ✓把包分 vendor(依赖、少变)和应用(你代码、常变)块——vendor 块长期缓存仅小块应用块部署时重下。
  • ✓从可摇包用命名导入(import { debounce } from 'lodash')使打包器消不用的 69KB——或换全可摇的 lodash-es。
  • ✓体积意外增长时用 webpack-bundle-analyzer 分析包——可视化器立即显元凶(大依赖、重复导入、漏代码分割)。