包体积
保持 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 分析包——可视化器立即显元凶(大依赖、重复导入、漏代码分割)。