因为并不是项目的创建者,所以很多配置方面并没有去过多的去关注,但是最近在一台带宽极低的客户机器上部署时,发现首屏加载的速度极慢,于是乎我开始了对项目的瘦身改造。

运维层面

gzip

按下F12,开启切图仔的利器,先来“称称体重(lighthouse)”

old-per

TTI(Time To Interactive)有点长(在低带宽机器上打开巨慢也是因为这个原因),打开 network 面板,发现打包后的 bundle 非常大,因为是 SPA,所有的内容内在 bundle.js 中,所以这个文件会非常大,并且没有开启 gzip 压缩,这几乎是网络 IO 中的耗时最大的。

但是我点开详情之后却发现了一个问题,css 的 bundle 是经过 gzip 的

image-20221114104117422

但是JavaScript 缺没有 gzip

old-net

我第一反应是 gzip 规则没有命中 js 的资源,果然,在查看了容器挂载的 configMap 之后发现 js 资源的匹配规则有多种格式,唯独少了网络面板中Content-Type 现实的格式。

nginx-conf

我直接就给加了上去,很快哈!保存配置,删除 pod,等待 pod 重新创建,回到页面刷新。

欸,效果有了

new-net

可以看到 JavaScript 资源已经开启了 gzip,并且传输体积也从 8.8M 减小到了 3.8M,再来“上个称”

new-per

这还没动代码,就已经效果显著了😄,下面我们将陆续对代码层面动刀

代码层面

先对模块进行一下分析,由于框架使用的Umi,内置了 analyze 模块,只需要通过环境变量开启即可(webpack环境下使用 webpack-bundle-analyzer 插件)

{
  "build": "ANALYZE=1 umi build"
}

构建完成之后自动打开页面

image-20221201103739556

此时构建的 bundle 大小为

image-20221205104307120

按需引入

左上角有一块巨大的面积标有 icon/es,这个是 antd 的图标库image-20221207194341062

由于之前同事在写 layout 组件的时候,直接将 icon 全部引入,通过路由配置的图标名称进行加载,所以打包产物中有全部的图标

import * as AntIcons from '@ant-design/icons';
// 省略其它代码
// Menu配置中的Icon从String转React Components
function configureIconsForRoutes(route: IRoute) {
  const newRoute = { ...route };
  // 如果有Icon
  if (typeof route.icon === 'string') {
    // 如果在Ant Design Icon列表中
    // @ts-ignore
    if (AntIcons[route.icon]) {
      // @ts-ignore
      newRoute.icon = React.createElement(AntIcons[route.icon], null);
    }
  }

  // 如果有子路由
  if (route.routes) {
    newRoute.routes = route.routes.map((route) => {
      return configureIconsForRoutes(route);
    });
  }
  return newRoute;
}

我们将所需的图标按需加载,然后导出交由 layout 进行加载,即可在最小影响下进行优化

import {
  ClusterOutlined,
  GoldOutlined,
  NodeIndexOutlined,
  BlockOutlined,
  GroupOutlined,
  AppstoreOutlined,
  CloudServerOutlined,
  MessageOutlined,
  VideoCameraOutlined,
  AlertOutlined,
  SettingOutlined,
  AppstoreAddOutlined
} from '@ant-design/icons'

export default {
  ClusterOutlined,
  GoldOutlined,
  NodeIndexOutlined,
  BlockOutlined,
  GroupOutlined,
  AppstoreOutlined,
  CloudServerOutlined,
  MessageOutlined,
  VideoCameraOutlined,
  AlertOutlined,
  SettingOutlined,
  AppstoreAddOutlined
}

// layout 组件
import AntIcons from './components/icons'

来看一下优化后的效果。Amazing!!!直接缩小 n 倍

image-20221207200801413

除了图标库,还有一坨巨大的 echarts,同样是可以通过按需引入来进行优化

image-20221205110048902

在使用echarts 的时候,基本上都是全量引入的,如下图

image-20221207201927979

但其实 echarts 提供了按需加载的方案

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

// 注册必须的组件
echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  TransformComponent,
  BarChart,
  LabelLayout,
  UniversalTransition,
  CanvasRenderer
]);

// 接下来的使用就跟之前一样,初始化图表,设置配置项
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
  // ...
});

根据官方的文档,还是按照最小改动的原则,具体的操作就不写了,这个看具体使用了什么图表,如果漏写了某个图表或者组件,可以根据浏览器控制台的报错提示进行添加

优化后的体积减小了 50%

image-20221207213632659

无用的依赖

经过按需加载的优化,现在打包后的 bundle 体积如下

image-20221207214135559

此时还有一些依赖项是从公司的业务组件库中带进来的一些库,但是在我们的产品中没有使用,可以将某些包通过 CDN 的形式加载。

{
  // ...其他配置省略
  externals: {
    xterm: 'window.xx',
  },
  scripts: [
    https://cdn.xx.xx
  ]
}

如果确定这个依赖不会使用,可以不添加 cdn 地址,直接放弃打包

momentjs 优化

momentjs 是一个经典的时间库,但是他的打包后的体积很大,因为其内部带有多个语言支持

image-20221207222345801

在此项优化上我们有两种方式

  1. 使用 dayjs 替换 momentjs,两者的 API 很接近,替换的工作量很小
  2. 开启 umi 的ignoreMomentLocale选项,来忽略 momentjs 的语言支持

去掉语言包的 momentjs 体积大大减小

image-20221207222701293

经过优化,此时的产物体积已经小了 2.7M

image-20221207224619530


前端小白