因为并不是项目的创建者,所以很多配置方面并没有去过多的去关注,但是最近在一台带宽极低的客户机器上部署时,发现首屏加载的速度极慢,于是乎我开始了对项目的瘦身改造。
运维层面
gzip
按下F12,开启切图仔的利器,先来“称称体重(lighthouse)”
TTI(Time To Interactive)有点长(在低带宽机器上打开巨慢也是因为这个原因),打开 network 面板,发现打包后的 bundle 非常大,因为是 SPA,所有的内容内在 bundle.js 中,所以这个文件会非常大,并且没有开启 gzip 压缩,这几乎是网络 IO 中的耗时最大的。
但是我点开详情之后却发现了一个问题,css 的 bundle 是经过 gzip 的
但是JavaScript 缺没有 gzip
我第一反应是 gzip 规则没有命中 js 的资源,果然,在查看了容器挂载的 configMap 之后发现 js 资源的匹配规则有多种格式,唯独少了网络面板中Content-Type 现实的格式。
我直接就给加了上去,很快哈!保存配置,删除 pod,等待 pod 重新创建,回到页面刷新。
欸,效果有了
可以看到 JavaScript 资源已经开启了 gzip,并且传输体积也从 8.8M 减小到了 3.8M,再来“上个称”
这还没动代码,就已经效果显著了😄,下面我们将陆续对代码层面动刀
代码层面
先对模块进行一下分析,由于框架使用的Umi,内置了 analyze 模块,只需要通过环境变量开启即可(webpack环境下使用 webpack-bundle-analyzer 插件)
{
"build": "ANALYZE=1 umi build"
}
构建完成之后自动打开页面
此时构建的 bundle 大小为
按需引入
左上角有一块巨大的面积标有 icon/es,这个是 antd 的图标库
由于之前同事在写 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 倍
除了图标库,还有一坨巨大的 echarts,同样是可以通过按需引入来进行优化
在使用echarts 的时候,基本上都是全量引入的,如下图
但其实 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%
无用的依赖
经过按需加载的优化,现在打包后的 bundle 体积如下
此时还有一些依赖项是从公司的业务组件库中带进来的一些库,但是在我们的产品中没有使用,可以将某些包通过 CDN 的形式加载。
{
// ...其他配置省略
externals: {
xterm: 'window.xx',
},
scripts: [
https://cdn.xx.xx
]
}
如果确定这个依赖不会使用,可以不添加 cdn 地址,直接放弃打包
momentjs 优化
momentjs 是一个经典的时间库,但是他的打包后的体积很大,因为其内部带有多个语言支持
在此项优化上我们有两种方式
- 使用 dayjs 替换 momentjs,两者的 API 很接近,替换的工作量很小
- 开启 umi 的ignoreMomentLocale选项,来忽略 momentjs 的语言支持
去掉语言包的 momentjs 体积大大减小
经过优化,此时的产物体积已经小了 2.7M