React可视化项目打包优化

1,729 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

1. 背景

前不久接了一个大屏可视化项目,后端服务不动,前端页面按照新版UI重新开发,由于是在前人的基础上做开发,就没有考虑其他方面,等开发完成,部署上线后,发现首次加载需要很长时间,查看资源大小,发现vendors.js体积过大,达到了1.8M,耗时1.2s左右,虽然甲方没有性能优化的需求,可我实在看不下去了,帮他们优化一下吧。

image.png

2. 安装webpack-bundle-analyzer

安装命令:npm install --save-dev webpack-bundle-analyzer或者yarn add -D webpack-bundle-analyzer

3. 使用webpack-bundle-analyzer

由于项目是以前的开发人员用webpack搭建的,找到webpack.prod.conf.js,添加如下代码:

// ...
import WebpackBundleAnalyzer from 'webpack-bundle-analyzer';
// ...
const devWebpackConfig = merge(baseWebpackConfig, {
  // ...
  plugins: [
    // 依赖包大小分析
    new WebpackBundleAnalyzer.BundleAnalyzerPlugin()
  ]
  // ...
})

4. 打包项目

运行npm run build命令,浏览器会自动打开如下页面,发现vendors.js包含了整个echarts库及其他三方库。

image.png

5. 按需引入echarts

由于项目使用了echarts-for-reactecharts的组合,所以需要按需引入两者,首先按需引入echarts-for-react,然后打开echarts官方文档,按需引入(PS: 项目用的echarts@4.9.0,而官方文档默认是5.x),所以终端会报以下错误:

image.png

出现这个问题,第一反应就是找到node_modules文件夹下面的echarts目录,看看有没有对应的文件,如下图

image.png

发现并没有core目录,于是怀疑是版本问题,于是查看了package.json,对比echarts的版本:

image.png

果然与官方文档的版本不同,于是打开npm官网,查找echarts@4.9.0版本的官网地址,打开官网,点击网站底部的切换旧版本,即为echarts4.x的文档,顺便也把5.x的按需引入记录一下。

5.1 按需引入echarts 5.x

// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
import { 
    BarChart,
    LineChart,
    PieChart,
    MapChart
    // ScatterChart
} from 'echarts/charts';
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
    TitleComponent,
    LegendComponent,
    TooltipComponent,
    GridComponent,
    DatasetComponent,
    TransformComponent,
    GeoComponent
} from 'echarts/components';
// 标签自动布局,全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
    TitleComponent,
    LegendComponent,
    TooltipComponent,
    GridComponent,
    DatasetComponent,
    TransformComponent,
    GeoComponent,
    LabelLayout,
    UniversalTransition,
    CanvasRenderer,
    BarChart,
    LineChart,
    PieChart,
    MapChart
    // ScatterChart
]);

5.2 按需引入echarts-for-reac和echarts 4.x

import ReactEchartsCore from 'echarts-for-react/lib/core';
import echarts from 'echarts/lib/echarts';
// 按需引入图表
import 'echarts/lib/chart/bar'; // 柱状图需要
import 'echarts/lib/chart/line'; // 折线图需要
import 'echarts/lib/chart/lines'; // 多折线图需要
import 'echarts/lib/chart/pie'; // 饼图需要
import 'echarts/lib/chart/map'; // 地图需要
import 'echarts/lib/component/geo'; // 地图需要
import 'echarts/lib/chart/effectScatter'; // 散点图需要

// 按需引入组件
import 'echarts/lib/component/title'; // 标题组件
import 'echarts/lib/component/legend'; // 图例组件
import 'echarts/lib/component/tooltip'; // hover组件
import 'echarts/lib/component/grid'; // 坐标组件
import 'echarts/lib/component/dataZoom'; // 区域缩放组件
import 'echarts/lib/component/polar'; // 极坐标组件

export default class EchartBase extends Component {
  render() {
    return (
      <ReactEchartsCore
        echarts={echarts}
        option={this.getOption()}
        notMerge={true}
        lazyUpdate={true}
        theme={"theme_name"}
        onChartReady={this.onChartReadyCallback}
        onEvents={EventsDict}
        opts={}
      />
    )
  }
}

5.3 记录按需引入出现的错误

5.3.1 Uncaught TypeError: Cannot read properties of undefined (reading 'findAxisModel')

image.png

解决方法:

// ...
import 'echarts/lib/component/polar';
// ...

5.3.2 Uncaught Error: Component series.effectScatter not exists. Load it first.

image.png

解决方法:

// ...
import 'echarts/lib/chart/effectScatter';
// ...

5.3.3 Uncaught Error: Component series.lines not exists. Load it first.

image.png

解决方法:

// ...
import 'echarts/lib/chart/lines';
// ...

5.4 按需加载echarts后的效果

image.png

可以看到vendors.js由最初的1.8M左右减小到了1.2M左右。

6. @ant-design/icons/lib

image.png

注意到vendors.js包含了 antd@3.26.20 UI库,根据官网的说明,ant-design默认开启了tree shaking,上图右边红框里面有一个antd/es模块,只有110KB左右大小,而且里面包含的组件也是项目里面实际引入的,说明是正常的按需加载,但是上图左边红框的@ant-design/icons/lib模块,足足有500KB左右,项目里面根本没有使用ant-design的icon,却把icon全部打包进来了。

image.png

查看 antd@3.26.20 官方文档得知3.9.0之后的版本全量引入了所有icon,所以打包体积会显著增加,由于项目使用的版本是3.26.20,所以也存在这个问题。

6.1 方法一:使用webpack-ant-icon-loader@^1.0.8(不推荐)

通过使用 webpack-ant-icon-loader@^1.0.8@antd-design/icons/lib/dist的图标文件拆分成独立的chunk,且异步加载后自动注册图标,详细内容可以查看插件的readme,配置如下:

// webpack.base.conf.js
export default function() {
  return {
    // ...
    optimization: {
      splitChunks: {
        minSize: 10,
        minChunks: 1,
        chunks: function(chunk) {
          // 这里的name 可以参考在使用`webpack-ant-icon-loader`时指定的`chunkName`
          return chunk.name !== 'antd-icons';
        },
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors'
          }
        }
      },
      noEmitOnErrors: true
    },
    modules: {
      rules: [
        // ...
        {
          loader: 'webpack-ant-icon-loader',
          enforce: 'pre',
          options: {chunkName: 'antd-icons'},
          include: [require.resolve('@ant-design/icons/lib/dist')]
        }
      ]
    }
    // ...
  }
}

打包效果如下:

image.png

可以看到之前vendors.js里面的@antd-design/icons/lib/dist图标文件,被单独拆分成antd-icons.js,这时vendors.js只有824KB左右了。

6.2 方法二:升级到antd到4.x(推荐)

从 4.0 开始,antd 不再内置 Icon 组件,请使用独立的包 @ant-design/icons

且评估了升级版本不会带来额外的问题,因此直接升级到 antd@^4.19.5 效果如下:

image.png

可以看到vendors.js中的红框部分正是icon图标,vendors.js整体体积减小到了780KB左右。

7. 接下来的事情

  • 1. vendors.js体积还是比较大,一般webpack对超过300KB的文件出产生警告,将继续拆分独立的chunk
  • 2. 启动项目的端口固定是8080,如果该端口被占用,将无法启动,将参考vue-cli修改成搜索没有被占用的端口作为启动端口;