使用webpack-bundle-analyzer 对 react 老项目进行打包优化

文章目录

概要

近期在维护一个老项目的时候,发现它跑起来太慢了,核心文件过大如下图,实在是受不了,准备对它下手。

整体架构

项目使用的是 react 16 +antd+webpack

优化方案

通过安装webpack-bundle-analyzer 来对打包后的产物进行分析,然后逐个击破。

过程

主要问题出现在以下几个包如图:





分析上图后得出以下方案

  1. 44.chunk.js (5.54 MB) - antd 和 @ant-design/icons
    问题:@ant-design/icons 体积很大,且 antd 使用了 lib 目录而非 es。
javascript 复制代码
// .babelrc - 修改 antd 按需加载配置
[
  "import",
  {
    "libraryName": "antd",
    "libraryDirectory": "es",  // 改为 es,体积更小
    "style": true
  }
],
// 添加 @ant-design/icons 按需加载
[
  "import",
  {
    "libraryName": "@ant-design/icons",
    "libraryDirectory": "es/icons",
    "camel2DashComponentName": false
  },
  "icons"
]

代码中按需导入图标:

javascript 复制代码
// ❌ 不要这样
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';

// ✅ 改为这样
import PlusOutlined from '@ant-design/icons/es/icons/PlusOutlined';
import DeleteOutlined from '@ant-design/icons/es/icons/DeleteOutlined';
  1. lodash 优化
    问题:lodash 使用方式不一致,可能全量引入。
javascript 复制代码
// .babelrc - lodash 插件已配置,确保使用正确
// 代码中统一使用按需导入
// ❌ 不要这样
import _ from 'lodash';
const result = _.debounce(fn, 300);

// ✅ 改为这样(已有部分代码这样做了)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
  1. immutable.js 去重
    问题:immutable.js 在多个 chunk 中重复出现。
    优化:在 webpack.config.js 中添加去重配置:
javascript 复制代码
// config/webpack.config.js - 在 optimization.splitChunks 中启用
splitChunks: {
  chunks: 'all',
  name: false,
  cacheGroups: {
    // immutable.js 单独打包,避免重复
    immutable: {
      test: /[\\/]node_modules[\\/]immutable[\\/]/,
      name: 'immutable',
      priority: 20,
      reuseExistingChunk: true,
    },
    // antd 单独打包
    antd: {
      test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
      name: 'antd',
      priority: 15,
      reuseExistingChunk: true,
    },
    // rc-* 组件单独打包(antd 的底层依赖)
    rcComponents: {
      test: /[\\/]node_modules[\\/]rc-[\\w-]+[\\/]/,
      name: 'rc-components',
      priority: 10,
      reuseExistingChunk: true,
    },
    // 其他第三方库
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: 'vendors',
      priority: 5,
      reuseExistingChunk: true,
      minChunks: 2,
    },
  },
},
  1. echarts 按需加载优化
    问题:echarts 体积大,需要更细粒度的按需加载。
javascript 复制代码
// 如果某个页面只需要特定图表类型,可以这样:
import echarts from 'echarts/lib/echarts';
import 'echarts/lib/chart/bar';  // 只引入需要的图表类型
import 'echarts/lib/component/tooltip';
  1. video.js 动态导入
    问题:video.js 体积大(2.08 MB),但可能不是所有页面都需要。
    优化:改为动态导入:
javascript 复制代码
// ❌ 当前方式
import videojs from 'video.js';

// ✅ 改为动态导入
const loadVideoJS = () => import('video.js').then(module => module.default);

// 使用时
componentDidMount() {
  loadVideoJS().then(videojs => {
    // 使用 videojs
  });
}
  1. UEditor 动态导入
    问题:UEditor 体积很大(包含 jquery),不是所有页面都需要。
javascript 复制代码
// web_modules/qmkit/ueditor/Ueditor.tsx
// ✅ 改为动态导入
const UEditor = React.lazy(() => import('./Ueditor'));

// 使用时用 Suspense 包裹
<Suspense fallback={<div>加载中...</div>}>
  <UEditor />
</Suspense>
  1. moment.js 优化
    问题:moment.js 包含所有语言包,体积大。
    优化:在 webpack.config.js 中移除未使用的语言包:
javascript 复制代码
// config/webpack.config.js - 在 plugins 中添加
new webpack.IgnorePlugin({
  resourceRegExp: /^\.\/locale$/,
  contextRegExp: /moment$/,
}),

// 如果只需要中文,确保代码中只引入中文
import 'moment/locale/zh-cn';  
  1. 启用更激进的代码分割
    优化 webpack.config.js:
javascript 复制代码
// config/webpack.config.js
optimization: {
  // ... 其他配置
  splitChunks: {
    chunks: 'all',
    name: false,
    minSize: 20000,  // 20KB,小于此大小的不单独打包
    maxSize: 500000, // 500KB,超过此大小尝试分割
    cacheGroups: {
      // React 核心
      react: {
        test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
        name: 'react-core',
        priority: 30,
        reuseExistingChunk: true,
      },
      // immutable
      immutable: {
        test: /[\\/]node_modules[\\/]immutable[\\/]/,
        name: 'immutable',
        priority: 25,
        reuseExistingChunk: true,
      },
      // antd
      antd: {
        test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
        name: 'antd',
        priority: 20,
        reuseExistingChunk: true,
      },
      // rc-* 组件
      rcComponents: {
        test: /[\\/]node_modules[\\/]rc-[\\w-]+[\\/]/,
        name: 'rc-components',
        priority: 15,
        reuseExistingChunk: true,
      },
      // echarts
      echarts: {
        test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
        name: 'echarts',
        priority: 15,
        reuseExistingChunk: true,
      },
      // 大型库单独打包
      largeLibs: {
        test: /[\\/]node_modules[\\/](video\.js|draft-js|ueditor)[\\/]/,
        name: 'large-libs',
        priority: 10,
        reuseExistingChunk: true,
      },
      // 其他第三方库
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        priority: 5,
        reuseExistingChunk: true,
        minChunks: 2,
      },
    },
  },
  runtimeChunk: {
    name: 'runtime',
  },
},

预期效果

总体打包体积减少了20%-30%。

可见把几个臃肿的文件拆了开来,而且总体积减小了。

小结

后期可以持续优化,比如项目中使用了 ueditor,可以替换成更轻量的库,比如 wangEditor; moment.js可以替换成 dayjs

相关推荐
沙蒿同学7 小时前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
cpolar技术支持7 小时前
本地 Playwright 测试报告怎么远程复盘?Trace Viewer 跑起来后,用 cpolar 分享失败现场
前端·自动化测试·测试工具·cpolar·playwright
wordbaby7 小时前
企业级后台管理系统路由设计与最佳实践指南
前端
胡志辉的博客7 小时前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
Hilaku8 小时前
作为面试官,我最怕遇到什么样的候选人?
前端·javascript·程序员
TiDi8 小时前
Pinia优化重复请求
前端
web3d5209 小时前
01-用 Leafletjs 10 分钟搭一张水利一张图(Vue3 + Vite 实战)
前端·javascript
晚安日记wanna9 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
打工仔折腾 AI9 小时前
用 Shell 脚本自动部署 mysqld_exporter 并接入 Prometheus 远程抓取
人工智能·后端·python·性能优化·ai agent 实战
TiDi9 小时前
吸顶导航交互实现
前端