我的前端项目构建时间从 8 分钟降到 40 秒,这 5 个优化起了关键作用

摘要:CRA 项目构建越来越慢,但又不想 eject 暴露所有配置。本文记录了在 CRA 框架限制下,通过依赖优化、esbuild-loader、分包策略、缓存和并行处理,将生产构建从 8 分钟压缩到 40 秒的真实过程。所有方法均无需 eject。

起因:构建一次,够下楼吃顿饭再上来

我们团队的前端项目基于 Create React App(CRA),没有 eject。项目跑了三年,从最初的 3 个页面膨胀到 15 个页面、200 多个组件,依赖从几十个涨到上千个。生产构建时间从最初的一分钟出头,逐步恶化到 8 分钟。

8 分钟意味着:提交 PR 后等 CI 构建,失败了改完再提交,又是 8 分钟。一天下来,光等构建就能浪费一个多小时。

团队讨论过是否 eject 或迁移到 Vite。但 eject 之后要自己维护 Webpack 配置,迁移 Vite 的改动量太大。最后我们决定在不 eject 的前提下,把能做的小优化全做了。结果远超预期------生产构建从 8 分钟降到了 40 秒。


诊断:先搞清楚时间都去哪了

优化之前,得先知道瓶颈在哪。我用 react-scripts build --profile 生成构建耗时报告:

bash 复制代码
npm run build -- --profile --json > stats.json

然后用 webpack-bundle-analyzer 和 speed-measure-webpack-plugin 分析耗时。

speed-measure-webpack-plugin 在 CRA 里需要一点技巧才能用。我们通过 CRACO(CRA Configuration Override)注入:

javascript 复制代码
// craco.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      return smp.wrap(webpackConfig);
    }
  }
};

跑完后,耗时分布一目了然:

阶段 耗时 占比
Babel 转译 3m 20s 42%
CSS/SCSS 处理 1m 50s 23%
Terser 压缩 1m 30s 19%
SourceMap 生成 40s 8%
其他 40s 8%
总计 ~8m 100%

Babel 转译和 Terser 压缩是两大头,占了总时间的 61%。优先砍这两块。


优化一:用 esbuild-loader 替换 Babel + Terser

Babel 是单线程的,Terser 也是。esbuild 用 Go 写成,多线程并行,速度有数量级的差异。

通过 CRACO 把 Babel 替换成 esbuild-loader,同时把 Terser 替换成 ESBuildMinifyPlugin:

bash 复制代码
npm install -D esbuild-loader @craco/craco
javascript 复制代码
// craco.config.js
const path = require('path');
const EsbuildPlugin = require('esbuild-loader').EsbuildPlugin;

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      // 1. 替换 Babel 为 esbuild-loader
      const oneOfRule = webpackConfig.module.rules.find(
        rule => rule.oneOf
      );
      
      if (oneOfRule) {
        const babelLoader = oneOfRule.oneOf.find(
          rule => rule.loader && rule.loader.includes('babel-loader')
        );
        if (babelLoader) {
          babelLoader.loader = 'esbuild-loader';
          babelLoader.options = {
            loader: 'tsx',     // 处理 TypeScript + JSX
            target: 'es2015',  // 目标环境
            tsconfigRaw: require('./tsconfig.json'),
          };
        }
      }

      // 2. 替换 Terser 为 esbuild
      if (webpackConfig.optimization) {
        webpackConfig.optimization.minimizer = [
          new EsbuildPlugin({
            target: 'es2015',
            css: true,  // 同时压缩 CSS
          }),
        ];
      }

      return webpackConfig;
    }
  }
};

改完之后,Babel 转译时间从 3 分 20 秒降到 12 秒,Terser 压缩从 1 分 30 秒降到 5 秒。两项合计从接近 5 分钟变成不到 20 秒。效果立竿见影。


优化二:分包策略------别把所有东西打成一个包

之前所有依赖都打进了一个 bundle,不仅构建慢,首屏加载也慢。把不常变的 vendor 拆出来,每次只构建变化的业务代码。

javascript 复制代码
// craco.config.js(追加到 webpack.configure 中)
const webpackConfig = ...;

webpackConfig.optimization.splitChunks = {
  chunks: 'all',
  cacheGroups: {
    // React 核心
    react: {
      test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
      name: 'vendor-react',
      priority: 40,
    },
    // Ant Design
    antd: {
      test: /[\\/]node_modules[\\/]antd[\\/]/,
      name: 'vendor-antd',
      priority: 30,
    },
    // 图表库
    charts: {
      test: /[\\/]node_modules[\\/](echarts|recharts)[\\/]/,
      name: 'vendor-charts',
      priority: 20,
    },
    // 其他依赖
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: 'vendor-common',
      priority: 10,
    },
  },
};

return webpackConfig;

配合路由懒加载(React.lazy),构建时只有变更的路由页面会重新打包,vendor 包缓存命中后直接跳过。


优化三:CSS 处理加速

项目里用了 SCSS,每次构建都要编译。改成 esbuild 处理 CSS 后,SCSS 编译时间从 1 分 50 秒降到 8 秒。

另一个容易被忽略的点是 Ant Design 的样式。Antd 5 用 CSS-in-JS,运行时注入样式,不需要在构建时编译 Less。如果还在用 Antd 4,建议升级,构建速度提升明显。

升级 Antd 的注意事项和具体迁移步骤,可以参考 gpt108.com 上整理的前端技术专题,里面有 Antd 4 到 5 的完整迁移指南和常见坑点汇总,对我们这次升级帮助很大。


优化四:构建缓存持久化

Webpack 5 支持持久化缓存到文件系统,配置后增量构建速度极快。CRA 默认没开,通过 CRACO 开启:

javascript 复制代码
// craco.config.js(追加)
webpackConfig.cache = {
  type: 'filesystem',
  buildDependencies: {
    config: [__filename], // 配置文件变化时缓存失效
  },
  cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'),
};

首次构建后,后续构建速度大幅提升。配合 CI 里的 actions/cache

yaml 复制代码
- name: Cache webpack
  uses: actions/cache@v4
  with:
    path: node_modules/.cache
    key: webpack-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.sha }}
    restore-keys: |
      webpack-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-

CI 里的增量构建时间从 8 分钟降到了第一次 40 秒,后续 15 秒(缓存命中)。


优化五:并行处理------能同时跑的都同时跑

之前的 CI 是串行的:lint → test → build。lint 和 test 之间没有依赖关系,完全可以并行。

yaml 复制代码
# .github/workflows/ci.yml
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run test

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run build

三个 job 并行跑,总时间从 lint + test + build(串行)变成了 max(lint, test) + build。虽然每个 job 单看没变快,但端到端的时间少了。


额外收益:开发体验也提升了

生产构建优化完,顺带把开发服务器的启动速度也优化了。npm start 从 45 秒降到 8 秒,热更新从 2 秒变成几乎实时。团队的开发体验好了很多。


优化效果总览

优化项 优化前 优化后 节省
Babel 转译 3m 20s 12s -3m 08s
Terser 压缩 1m 30s 5s -1m 25s
CSS 处理 1m 50s 8s -1m 42s
构建缓存(二次构建) 15s -
并行 CI 12m 端到端 3m 端到端 -9m
生产构建总耗时 ~8m ~40s -85%

8 分钟到 40 秒。提交 PR 后喝口水,构建已经跑完了。以前怕 CI 挂了反复等的焦虑感彻底消失。


踩过的坑

  1. esbuild-loader 不支持装饰器 :项目里用了 @observable 装饰器(mobx),esbuild 不支持。解决:装饰器的文件仍然走 Babel,其他文件走 esbuild。

  2. Webpack 缓存和 CI 缓存冲突 :CI 里 Webpack 缓存偶尔会因 node 版本不一致而报错。解决:CI 里指定固定的 node 版本号,并且 restore-keys 精确到 package-lock.json 哈希。

  3. 拆包后首屏变慢:vendor 包太多导致 HTTP 请求数增加。解决:控制 vendor 包数量不超过 5 个,配合 HTTP/2 的多路复用,体验没影响。


总结

CRA 项目的构建优化,核心思路就四个:

  1. 换工具:Babel → esbuild,Terser → esbuild。数量级提速。
  2. 拆包:vendor 和业务代码分开,减少重复构建。
  3. 开缓存:Webpack 持久化缓存 + CI 缓存,增量构建秒级。
  4. 并行跑:CI 里 lint、test、build 能并行的全并行。

不需要 eject,不需要迁 Vite,靠 CRACO 和几个 loader 就能做到。如果你的 CRA 项目构建超过 3 分钟,这周末试试,你会在周一的团队群里被同事感谢。


你的项目构建一次要多久?试过哪些优化方法?欢迎评论区交流。

相关推荐
Tbisnic1 小时前
AI大模型学习 第十天:让程序“指挥”大模型 —— 从对话到工具调用
人工智能·python·ai·大模型·react·cot·提示词工程
大任视点2 小时前
从云经济学之父,到人工智能经济学奠基人
大数据·人工智能·业界资讯
光锥智能2 小时前
库克“谢幕”,苹果AI“起航”?|苹果2026WWDC
人工智能
Mr.朱鹏2 小时前
科技资讯日报 · 2026-06-08
人工智能·科技·chatgpt
ai产品老杨2 小时前
【架构深评】打破多品牌壁垒:如何基于 GB28181 与 RTSP 栈,构建高解耦的 AI 视频流媒体管理平台?(附源码交付)
人工智能·架构·媒体
小丶舟2 小时前
6GB显卡本地AI效率提升实战:Ollama服务化+API调用+成本对比
人工智能
小龙报2 小时前
【AI全栈开发】一文打通AI时代的前后端开发核心概念
人工智能
AI探索先锋2 小时前
[特殊字符] Siri AI 炸场 WWDC!苹果联手谷歌 Gemini 打造“真·AI助手“,13人公司掀翻Transformer|AI科技热线
人工智能·transformer·wwdc
jinxindeep2 小时前
超越VLA与世界模型:构建下一代物理智能系统的四大支柱
人工智能