营销云项目性能优化方案
YXY 子应用首次加载情况分析
1. YXY 与 TMC 对比诊断
a. 典型页面指标对比分析
通过 Network 工具,结合 Core Web Vitals Annotation 工具,分别对 YXY 和 TMC 页面进行了核心指标收集。
简单页面对比 —— 分群列表页
DOM Content Loaded | Cumulative Layout Shift | First Contentful Paint | Largest Contentful Paint | 首次渲染和最大渲染差值 | 肉眼观察 | |
---|---|---|---|---|---|---|
营销云 | 0.91s | 0.0343 | 2.25s | 4.85s | +2.6s | +4s |
TMC | 0.94s | 0.00958 | 1.55s | 2.44s | +0.89s | +1.5s |
复杂页面对比 —— 数据看板页
DOM Content Loaded | Cumulative Layout Shift | First Contentful Paint | Largest Contentful Paint | 首次渲染和最大渲染差值 | 肉眼观察 | |
---|---|---|---|---|---|---|
YXY | 0.98s | 0.111 | 2.34s | 4.34s | +2s | +5s |
TMC | 0.91s | 0.0343 | 2.25s | 4.85s | +2.6s | +4.5s |
通过对于典型复杂页面和简单页面的核心指标采集对比:
- 随着页面复杂度的上升,加载内容也随之增加。由第一项 DCL(内容加载性能指标)可以看出,YXY 环境随着加载内容变多,加载性能下降较快。
- 简单页面和复杂页面上,YXY 的 CLS(页面布局偏移指标)都略微偏高。
- 对于 FCP(首次内容渲染耗时)和 LCP(最大内容渲染耗时),对比简单页面和复杂页面,二者出现了相反的结论。
- 在简单页面中 YXY 的首次内容和最大内容渲染都略慢于 TMC
- 在复杂页面中,首次内容渲染二者接近,最大内容则相反 YXY 比 TMC 渲染速度更快
带着这些疑问,利用性能分析工具,接下来做进一步的分析。
b. 网页核心指标对比结果原因分析
Cumulative Layout Shift 值高的原因
根据前文表格观察,由于 CLS 值的偏高是跨场景页面下的普遍偏高一点。推测是公共部分的影响导致了偏移量的增加 通过性能工具定位,如下图,
- 由于侧面菜单加载是异步的,侧菜单未预留默认宽度,导致侧面菜单渲染完毕后,右侧主内容被挤向右移动,导致了偏移量 CLS 值的上升。
解决方式: 优化侧边菜单加载。使用定宽 dom,在数据未加载完成之前,依旧占有一定宽度,防止引发对于主页面的挤压漂移
- 页面整体内框以及菜单边缘未设置默认宽度,都采取了内部元素撑开的方式进行填充,引发了 CLS 值的略微偏高。
偏移量整体本身对页面加载速度没有影响,但是会引发布局的变化,触发局部的重排和重绘,进而产生加载后抖动的不佳感受。 解决方式: 如果存在宽高在渲染过程中会发生变化的的元素场景时,给元素添加定值默认宽高,防止异步加载完成后,自身尺寸发生变化,对其他内容产生挤压,触发重排和重绘,降低性能。
First Contentful Paint 值相对慢了 0.7s
从性能请求瀑布流上大致可以看出,主页面的数据请求从 3.8s 左右才开始发出,推断 YXY 采用了微前端的基座,基座处理和渲染占用了部分时间,主页面实际开始加载的时间靠后。 与此同时,TMC 没有加载基座,故首次渲染较快
解决方法: 优化基座加载子应用前置操作,提前预加载子应用。
- 对于首页控制台中有权限模块进行提前预加载
- 对于当前访问子应用相关联的子应用进行提前预加载
将部分可以异步操作的项目进行后置处理。同时子应用细化模块概念。对子应用内部大模块进行分 trunk 打包,进一步减少首屏加载内容,优化渲染时间。
Largest Contentful Paint,以及从初次渲染到最大渲染差值的差异
推测由页面列表中可交互内容的数量差异导致。 在 YXY 的列表中,除了和 TMC 系统中相同的层级列表、表格等展示能力外。 同时具备了表头实时筛选记忆、表头鼠标 hover 展示字段解读以及列状态操作和回显等能力。这些能力提供给用户更好的列表信息快速检索使用体验,同时加载渲染的复杂度也更高,导致了在渲染时间上 YXY 时间更长。
上述推论通过对比数据看板页的数据也可以看出。
在页面交互复杂度接近,展示内容接近的场景(如数据看板页)下,YXY 和 TMC 的渲染时间是相对接近的。
c. 技术架构实现的差异性
整体系统架构
- TMC - 整体单应用加载(teaUI & React)
- YXY - 基于 micro-app 的灯塔底座,可兼容多技术栈(qd-element-plus & Vue3 / element-ui & Vue2)的微前端架构。
从整体看,由于采用了基座 + 子应用的微前端架构,故在初始加载阶段 相较于 TMC 整体单应用的架构,YXY 的先基座后子应用的分步加载模式在整体加载上确实会有一定的劣势。 但与此同时,微前端的架构更适合目前 CDP、MA、BA 等多个子应用共同共存的模式。 该种架构下,子应用彼此直接相互独立,可自主迭代更新,互不影响。同时又共享基座所提供的共有能力,整体扩展度和鲁棒性更高。
资源加载
TMC - 静态资源利用 CDN 进行分发加速 YXY - 暂未使用 CDN 进行内容分发
页面的整体加载速度主要取决于:用户网络、当前访问命中的网络节点位置、以及服务的负载情况。 在用户的网络环境一定的情况下,资源加载优化的主要方向应当为网络节点的尽量优化上。
2. lighthouse 工具诊断
除去上述围绕 CWV 核心指标的诊断之外, 同时使用 chrome 的 lighthouse 性能分析工具协助评估诊断:
发现除去上述分析的问题之外,还存在下列几类问题:
移除阻塞渲染的资源
对于关键 CSS,以内联方式提供,并尽可能推迟可后置加载的 js 脚本。 作用: 此问题的优化,可以优化首次加载页面的前置加载阻塞,提升 FCP(首次内容渲染)指标
减少未使用的 Javascript
减少未使用的 JS 文件内容加载,并到需要使用的时候再加载相关内容。 作用: 未使用 JS 的优化, 可以优化首次加载页面的前置加载内容大小,提升 FCP(首次内容渲染)指标
减少未使用的 CSS(可提升哪几个指标)
抽离公共 CSS,去除未使用到的 CSS 文件。 作用: 同上,未使用 CSS 的优化, 可以优化首次加载页面的前置加载内容大小,提升 FCP(首次内容渲染)指标
避免大规模 DOM 场景的反复出现(可提升哪几个指标)
优化页面的展示逻辑,避免大规模 dom 同时展示在页面中 作用: 避免大规模 DOM 场景的出现,可以减少单词渲染消耗的时常 进而帮助 FCP(首次内容渲染)以及 LCP(最大内容渲染)值的提升
3. 性能数据分析面板诊断
与此同时,补充利用性能数据分析面板,进一步细化分析加载内容。可以发现:
相比之下,YXY 环境在首次渲染中出现了触发页面重新布局的强制操作,该操作出发了整体页面的重渲染,同时也影响到了页面的渲染速度。
4. 诊断总结
通过前面几种诊断方式共同定位下,目前 YXY 性能上主要存在的问题方向,按照优先级从高到低可罗列如下:
问题 | 问题方向 | 使用体感 |
---|---|---|
加载文件较大 | 部分文件未进行压缩混淆 部分文件中包含未使用到的部分 部分文件中包含重复引入的部分 | 加载文件感受较慢,页面载入等待时间较久 |
重复渲染、渲染较慢 | 部分模块前置逻辑中有强制重渲染逻 加载过程中包含了会阻塞渲染的 JS 长任务存在 渲染过程中出现一次性渲染元素过多 | 子应用主要部分加载较慢,页面主部分加载需要“等待一会”loading |
文件加载速度较慢 | 网络节点未优化,下载文件本身速度较慢 | 页面打开到开始 loading 中间有明显等待时间 |
页面流转和交互处理较慢 | 子应用未细化分包,加载时间较长 子应用间共享大文件未抽离,存在重复加载项目 | 页面流转和交互过程中有卡顿感 |
页面稳定性较差 | 菜单、动态尺寸元素未提供默认值 | 部分元素渲染完成后,尺寸相较初始状态有变化。整体页面产生“抖动”感觉 |
YXY SAAS 性能体验优化目标
1.1. 首次加载页面将更加迅速
通过从空间和时间两个维度对资源包大小以及加载方案的优化,在加载页面时能够显著提升页面加载的速度,减少加载占用的白屏时间,优化页面进入体验。
1.2. 页面流转会更加丝滑
网络层静态资源加载的优化,配合在页面切换时的动画以及骨架屏的加入,能够让用户在页面切换过程中,感觉更加流畅。
1.3. 内容操作更加顺畅
通过渲染层面的优化,提供虚拟列表、webassembly 等能力。今后在面对大数据量的列表、表格等场景,进一步提升页面计算性能,使得大数据量页面用户的操作更加流畅、不卡顿。
YXY SAAS 全局整体优化方案
通过前文的问题定位以及优化方向,接下来进一步细化具体的优化方案。
1. YXY 项目已经在使用中的性能优化方案
首先,目前 YXY 项目中已经使用了的优化方案有:
gzip 压缩
目前 YXY 项目对加载的静态文件已经开启了传输前 gzip 压缩,显著降低了用户打开页面时,加载的文件体积大小。提升加载速度与空间占用。
如上图,单个 js 文件采用 gzip 压缩之后,文件体积可以优化至原有的三分之一,提升明显。
使用 HTTP/2
开启 http2.0 默认具备了多路复用的能力,多条请求复用同一条连接通路,减少了在重复建立 TCP 的过程中的时间损耗。
目前请求协议都已切换至 http/2
打包体积优化
YXY 项目中,在项目打包的过程中,CDP、 MA、LM、PA、BA、AA 以及 通用组件库 已经针对大部份场景下 js、css 进行了混淆后的压缩,减少了文件包的体积。
缓存优化
目前项目中已经开启了强缓存、协商缓存配合浏览器本地缓存机制,一定程度上优化了页面重复短期内重复加载的问题。 在过期时间内重复刷新加载,满足条件的文件和请求会从本地内存中快速读取缓存值,节约请求加载时间。
2. 整体优化方案
a. 加载优化
时间维度加载优化
- 资源大小优化
现状: 目前系统中,子应用已经做到主应用与依赖分离打包,但子应用内部未按照模块进行细粒度拆分,整体应用打包在一起。 如 index-0d069bb8.js 这个文件为完整 CDP 模块的代码打包文件,这其中包含了 CDP 的标签、分群、数据资产等几大模块的所有代码。
现状问题: 目前 YXY 项目加载 js 文件时,是按照大模块进行的分包,如上图。 整个 CDP 模块打包进 index-0d069bb8.js 这一个 js 文件中,该文件主包有347k大小,加载时间403 毫秒。 这也就意味着,每次从外部进入到 CDP 的分群(或者任何一个模块页面)时,虽然我只需要查看分群模块相关的页面,但其他诸如标签、数据资产等页面的代码也会被同时加载。 加载了不必要的代码内容,拖慢了整体的加载速度。
解决方案: 对资源进行合理分包,禁止将应用所有文件打入到同一个 chunk 中,这样会导致首屏加载繁重的问题。 在子应用内部模块分包同时,配置随路由切换进行懒加载。 如:CDP 模块中,分群相关页面只加载分群页面的 js 文件,标签只加载标签 js 文件。
预期提升: 优化后,单个模块相关页面加载时,只会加载相应的代码 js 文件。 如下图,标签模块页面只会加载图中 tag 红框部分的代码,访问分群相关页面则只会加载 crowd 红框部分的代码。提升整体加载性能。
单个模块页面加载的 js 文件大小预计可以缩小到五分之一,加载时间也可提升 50%以上。
- 图片压缩
现状: 目前系统中展示的 png 类型图片,主要存放在代码仓库中,打包时候单独打出直接引用,没有做任何处理 系统中,用户上传文件类型涉及到图片时,利用 s3-sdk 直接进行的网盘上传,未做任何预处理
解决方案: 对于系统加载图片,在打包过程中对于图片进行 base64 转码后,再进行 gzip 压缩,进一步压缩图片大小。对 icon 图片转码打包压缩后,可以优化 50%的体积。
对于用户上传图片,体积较大的图片,可在上传模块中,引入 js 图片压缩工具(如:js-image-compressor)对用户上传的图片先进行压缩再上传至网盘。
如下工作流场景:图片可由用户进行任意上传,实际用户上传的图片体积可能会很大,而在页面中仅以一个 40x40 的小图片展示。该类型文件在上传过程中,应当进行压缩后保存。可以极大减少加载内容的体积。
预期提升: 对图片进行针对性优化后,对于页面加载速度,可有不同程度的提升。页面中图片内容越多,加载速度提升越明显。
图片类型优化
JPEG - 色彩丰富的图片 优点 - 压缩率高、兼容性好、色彩丰富 缺点 - JPEG 不适合用来存储企业 Logo、线框类的这种高清图,变化易失真;不支持动画、背景透明
PNG - logo、icon、需要支持透明的图片 优点 - 不失真的情况下尽可能压缩图像文件的大小、像素丰富、支持透明(alpha 通道) 缺点 - 文件大
SVG - 高保真,数据量不是很大的场景 优点 - 可伸缩性、Svg 平均比 GIF、 JPEG、 PNG 小得多,甚至在极高的分辨率下也是如此、支持动画、与 DOM 无缝衔接、可以直接使用 HTML、 CSS 和 JavaScript (例如动画)来操作 缺点 - SVG 复杂度高会减慢渲染速度、不适合游戏类等高互动动画
空间维度加载优化
- 依赖按需引入优化
现状: 部分模块在引入依赖时未按需引入,导致打包无法 tree-shaking。可能只用了某个依赖中的一个功能项,却打包打入了整个依赖包。
如果采用上图红框中的模块引用方式进行书写,系统打包时将默认将 703KB 的包整体打入应用中。而如果使用按需加载的写法,则只会打入使用到的子功能模块,其余部分皆会被抛弃不做打包。
解决方案: 对依赖方法统一调整为解构引入,只引入需要的功能模块,不作依赖包的整体引入。 开发规范中,新增对于依赖常用写法,规范按需加载。
预期提升: 举例目前该问题存在最普遍的 lodash 场景,预计可以节约三分之一的体积。
- 同类型依赖重复引用优化
现状: 部分模块中,由于开发同学不同,存在相同功能的不同依赖被重复引入。导致依赖冗余,共同占用包体积。
lodash-es 和 lodash 为相同能的依赖,被同时引入到了系统中。
其中 lodash-es 占用了703k的空间, lodash 占用了808k的空间。
解决方案: 排查同类型工具,对于相同职能工具做取舍型引入,减少相同类型工具的重复引入。举例:lodash-es & lodash
预期提升: 合并对齐前,整体依赖 vendor 包大小为 21.73M (gzip 之后为 2.5M)
合并对齐后
预计仅 lodash 一项的合并,可节约 0.72M 的加载内容
大文件依赖未独立打包优化
现状: 目前系统中存在部分大依赖文件,被打包入业务整体包中,导致该依赖即使在没有被使用的页面也会被初始化加载,拖慢整体加载速度。
如下图为 CDP 子应用的依赖模块分析图,其中红框部分的依赖:echarts 图标、S3-sdk、lodash 等这些依赖,在其他子应用(MA、BA、LM)中也会被大量使用。目前每个子应用都单独做了打包处理,故每个子应用最终包中都包含了这部分的重复内容引入。
解决方案: 对于体积较大文件,进行细粒度处理,独立生成 js 链接,以内联 link 的形式统一引入到系统中。使用到的页面加载文件,优化静态文件加载。
构建工具优化
现状: 目前系统中主要使用 vite 进行打包,并且在部分子应用中已经进行了 gzip 打包、分 trunk 打包等部分优化。于此同时还可以从多个角度引入构建优化工具进行进一步优化。
解决方案:
- 利用 vite-plugin-cdn-import 插件进行常用大型依赖的 cdn 加载
- 利用 vite-plugin-imagemin 插件进行图片的非转码有损压缩
- 在生产环境中通过打包移除不必要代码,如 debugger、注释等
预期提升 可以进一步提升文件加载速度,优化文件体积大小
b. 网络优化
引入 CDN 网络优化
现状: 网络资源请求过程中,使用 CDN 网络可以在下载资源的过程中,可以按照地理位置就近原则进行资源查询和下载。目前 YXY 项目静态资源暂未使用 CDN 网络,存在静态资源加载过慢的问题。
解决方案: 将静态资源介入 CDN 网络,优化资源的请求效率。
预期提升: 未使用 CDN 的加载速度
CDN 预计部署后,读取速度
DNS 优化
现状: 目前系统中未对于跨域请求的 DNS 缓存做预解析处理。初次访问状态下,会存在 DNS 寻址缓存的时间。 同时初次做 https 请求,会包含初始化建立 TCP 连接以及建立 SSL 加密层的时间,如下图。
解决方案: 通过在模版中注入如下 link 标签,进行跨域资源的预解析
<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin />
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
增加 dns-prefetch 后可以在相应静态资源请求前进行预解析,减少 DNS 查询阶段耗时。 同时配合 preconnect 进行预连接,在后续跨域请求发出时,就可以节约初次建立连接,以及 SSL 层建立的过程
预期提升: 首次加载页面根据网络情况以及各级缓存不同,预计可以节约 100ms 以上的寻址时间以及最多可达到 1s 以上的初次 TCP 和 SSL 层的建立时间。
重复请求优化
现状: 在排查过程中,发现部分应用页面存在重复请求的情况,造成网络资源浪费。
解决方案: 在应用网络层,集成节流控制能力。对于默认缓存时间(如 2s)内,请求地址、请求内容完全相同的,判定为重复请求。重复请求会在请求方本地进行默认读取上一次请求的结果,减少资源浪费,借助axios-cache-interceptor
。
预期提升: 在现在存在重复请求的页面中,将减少一次相同请求。视当前页面请求的重复程度而定,重复数量较多的,性能节约更大。
c. 渲染优化
超长列表以及超长横向表格
现状: 目前系统中存在长列表以及长表格场景。超出整屏展示界面,不可见但依旧会全部渲染,占用渲染内存。
当数据量大时,列数很多的场景中,渲染性能会浪费在很多不可见元素的渲染上。
解决方案:
对大数据量页面进行分片/分屏加载
只加载可视区域内的图表,对于不可见区域的图表,不作加载展示。同时增加缓冲区域,优化加载体验。
大数据量优化
现状: 目前系统中,前端暂未对可能的大数据量处理和展示做优化。
解决方案: 对于大数据量的页面,可以通过如下两种方式进行优化:
- webWorker - 对于耗时较长,可以作为独立异步模块的地方可以通过 worker 进行处理。处理同时不阻塞主渲染线程
- webAssembly - 对于涉及到超大数据量数据处理以及可视化图片处理的地方,可通过 webassembly 的方式进行优化。引入编译型语言弥补 Javascript 本身数据处理性能较弱的缺憾。
该种解决方案,在前端存在密集型计算的场景下(如前端的笛卡尔积运算、多层级复杂树形数据结构遍历等),可大幅度提升计算性能,减少 JS 运算对于视图渲染的阻塞。
对于数据量达到一定规模的图表,在展示渲染层面,可以进行以下优化:
- 超过一定阈值的数据,执行自动采样率降低,降低数据采样粒度,优化展示能力。
- 大数据量的内容计算加载,提供异步离线展示的形式
公共渲染区域顺序优化
现状: 菜单目前在部分子应用中完成了下沉至底座,菜单的渲染逻辑还不统一。
解决方案: 针对侧边菜单进行统一基座渲染,并预留初始占位。子应用切换时不再进行重新菜单渲染,节省渲染时间,优化切换体验。
d. 感知优化
Skeleton Screen 优化(P2)
现状: 目前页面渲染主要依赖于业务组件 or 全局加载 loading 进行加载中提示,未加载模块为空白+loading 标,较为直接。
解决方案: 引入骨架屏,从全局角度统一渲染加载流程,规范页面加载的统一一致性。 在页面内容未加载完成时,通过骨架屏占位给到页面结构的占位。
页面切换动效优化 - 动画参数
现状: 目前页面的流转为直接跳转,无任何过度态和动画。跳转过程和展示新页面过程较为生硬。
解决方案: 引入页面流转动画。全局统一做跳转新页面的过渡动画(如:左渐入等)。一方面提升用户使用感受,另一方面弱化新页面的加载时间。
3. 性能优化防劣化
通常任何一个系统,如果不对其进行干涉,他就一定会按照熵增的方向发展。 所以性能优化不是一蹴而就的工作,在持续进行优化的同时,也要做好性能优化防劣化的工作。
方案:
- 对于性能进行埋点监控。开发环境下自动捕捉当前状态下的页面核心指标值
- 开发场景下,对于当前页面进行性能指标的实时监控。既不影响生产环境,同时在开发同时可以实时看到修改对于当前环境的性能影响,及时提示。
- 对于主干测试环境进行定期数据采集,阈值配置。当某模块运行时达到阈值,进行实时告警。以企微机器人、邮件推送等方式通知模块负责人,提醒及时修复
性能体验度量核心标准
度量指标 | 现状 | 优化目标 | 标准(google 核心指标标准) |
---|---|---|---|
FCP | 1.52372 | < 1.5 | 0 - 1.8s 业界前列 1.8s - 3s 中等水平 >3s 需要优化 |
CLS | 0.16 | < 0.1 | < 0.1 业界前列 0.1 - 0.25 中等水平 >0.25 需要优化 |
LCP | 2.56046 | < 2.5 | < 2.5s 业界前列 2.5s - 4.0s 中等水平 >4.0 s 需要优化 |
肉眼观感 | 1. 页面流转有较短时间的卡顿 2. 刷新页面有明显等待时间 3. 部分页面加载过程中,存在页面抖动 | 1. 页面流转切换更加顺畅 2. 页面刷新速度更快,动画体验更佳 3. 减少加载过程中页面的抖动感和卡顿感 |
优化结果
1. 静态资源接入 CDN
模块静态资源、组件库以及第三方依赖,均已部署开启 CDN 接入前: 370k 文件,加载时间为 295ms
接入后: 366k 文件,加载时间为 159ms,加载时间减少 40%
2. 大文件拆包
子应用进行业务组件、第三方依赖以及按模块拆包 拆包前: 整体子应用包打进 index-xxxxx.js 单一文件中,一起加载 整体包 4.4M,加载耗时 5.1s
拆包后: 子应用拆分成:
- 项目本体 - index-xxxxxx.js
- 业务组件 - vendor-qd-xxxxxx.js
- 第三方依赖 - vendor-xxxxxx.js
分开加载,其中后两者异步加载, 整体加载 3.7M,体积优化 15% 加载耗时 3.3s,加载速度优化 35%
未完待续。。。