--- id: frontend.webpack.scenario.build-speed title: 大型项目构建慢、包体积大:度量、优化和防退化 type: scenario domain: frontend stack: [webpack, fe-testing-perf, cicd] tags: [构建速度, 包体积, splitChunks, 持久化缓存, 体积预算, CI] level: intermediate prerequisites: [frontend.webpack.build-optimization] est_hours: 2 related: [frontend.webpack.interview, frontend.vite.scenario.webpack-to-vite] week: [7] --- # 大型项目构建慢、包体积大:度量、优化和防退化 ## 业务背景与问题 一个中台项目(webpack 5,几百个页面):CI 构建要十几分钟,发版排队;首页 JS 有几 MB,弱网用户首屏很慢;优化过一次,几个月后又变回去了。 ## 约束 - 短期内不能换构建工具(有大量自定义 loader 和插件)。 - 优化要可量化,并且防止退化。 ## 方案设计 1. **先度量**: - 构建速度:统计各 loader / plugin 耗时(速度分析插件或 `--profile`); - 体积:生成产物分析报告(`webpack --json` + 可视化工具),找出最大的依赖和重复依赖。 2. **构建提速**: - 开启 `cache: { type: "filesystem" }`,CI 中缓存缓存目录; - babel-loader 换成 swc 类 loader,并用 `include` 只处理源码; - 生产构建关闭不需要的 source map 或使用更快的类型; - 类型检查与构建并行(单独的 `tsc --noEmit` 任务)。 3. **体积优化**: - 路由级懒加载; - 大依赖按需引入(图表库、组件库、日期库)或替换为更小的库; - `splitChunks` 按变化频率拆分第三方库; - 检查 `sideEffects` 让 tree shaking 生效。 4. **防退化**:在 CI 中设置体积预算(`performance.maxEntrypointSize` 或脚本对比上次的产物大小),超出就失败并提示。 ## 关键代码 ```js // webpack.config.js 片段 export default { cache: { type: "filesystem", buildDependencies: { config: [import.meta.filename] } }, // 配置变化时缓存失效 module: { rules: [{ test: /\.[jt]sx?$/, include: path.resolve("src"), use: "swc-loader" }], }, optimization: { runtimeChunk: "single", splitChunks: { chunks: "all", cacheGroups: { framework: { test: /[\\/]node_modules[\\/](vue|vue-router|pinia)[\\/]/, name: "framework", priority: 20 }, vendors: { test: /[\\/]node_modules[\\/]/, name: "vendors", priority: 10 }, }, }, }, performance: { hints: "error", maxEntrypointSize: 500 * 1024, maxAssetSize: 300 * 1024 }, // 超预算直接构建失败 }; ``` 可运行的基础配置:[examples/webpack-config](../examples/webpack-config/)。 ## 取舍 | 选择 | 优点 | 缺点 | | --- | --- | --- | | **在 webpack 内优化(本方案)** | 风险小,见效快 | 上限受 webpack 限制 | | 迁移到 Rspack | 配置大体兼容,速度提升大 | 部分插件需要验证 | | 迁移到 Vite | 开发体验最好 | 迁移成本高,见[迁移场景](../../vite/scenarios/webpack-to-vite.md) | ## 易错点 - 没有度量就开始优化,改了很多却不知道哪项有效。 - 持久化缓存没有配置 `buildDependencies`,改了配置却用了旧缓存。 - 把所有第三方库打成一个巨大的 vendor,任何依赖升级都让整个文件缓存失效。 - 只优化一次,没有预算和 CI 检查,很快退化。 ## 面试怎么讲 > “先度量:用速度分析和产物分析找到瓶颈,主要是 babel 处理了 node_modules 和一个整包引入的图表库。然后开启持久化缓存并在 CI 中缓存、换成 swc 类 loader、类型检查并行;体积上做路由懒加载、图表库按需引入、按变化频率拆包。最后在 CI 加体积预算防止退化。数据以实际测量为准,我会给出优化前后的对比。”