前端性能优化一直是衡量一个团队和一个前端的各方面水平,呈现快速的加载,是给人最直观,成就感也最足的一个感受,而且对用户体验是第一重要的概念,所以这个相当重要,下面就来结合美团的实践方案来讨论一下。
其实文中的很多方案,我和我团队也早在实践了,比如服务端渲染,nginx转发等等,今天发出来,是因为恰巧最近又在搞性能优化问题,大家不妨也看看,美团团队在半年前发的这篇总结,很全面也很详细了。
以下一段转载自:美团技术团队分享
自JavaScript诞生以来,前端技术发展非常迅速。移动端白屏优化是前端界面体验的一个重要优化方向,Web 前端诞生了 SSR 、CSR、预渲染等技术。前端服务端渲染、后端服务端渲染等。
在美团支付的前端技术体系里,通过预渲染提升网页优化,从而优化了白屏问题,提升用户体验,并形成了最佳实践。
在前端渲染领域,主要有以下几种方式可供选择:
通过对比,同构方案集合 CSR 与 SSR 的优点,可以适用于大部分业务场景。
结合到我们团队负责的支付业务场景里,在保证系统稳定性的前提下,还需要保障用户体验,所以采用了预渲染的方式。
那么究竟什么是预渲染呢?我们先从最常见的 CSR 开始说起。
以 Vue 举例,常见的 CSR 形式如下:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
一切看似很美好。然而,作为以用户体验为首要目标的我们发现了一个体验问题:首屏白屏(SPA都会遇到的问题)。
为什么会首屏白屏
浏览器渲染包含 HTML 解析、DOM 树构建、CSSOM 构建、JavaScript 解析、布局、绘制等等,大致如下图所示:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
要搞清楚为什么会有白屏,就需要利用这个理论基础来对实际项目进行具体分析。通过 DevTools 进行分析:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
-
等待 HTML 文档返回,此时处于白屏状态。
-
对 HTML 文档解析完成后进行首屏渲染,因为项目中对加了灰色的背景色,因此呈现出
灰屏。
-
进行文件加载、JS 解析等过程,导致界面长时间出于灰屏中。
-
当 Vue 实例触发了 mounted 后,界面显示出大体框架。
由此得出,因为要等待文件加载、CSSOM 构建、JS 解析等过程,而这些过程比较耗时,导致用户会长时间出于不可交互的首屏灰白屏状态,从而给用户一种网页很“慢”的感觉。
二 优化思路
在User-centric Performance Metrics一文中,共提到了4个页面渲染的关键指标:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
基于这个理论基础,再回过头来看看之前项目的实际表现:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
可见在 FP 的灰白屏界面停留了很长时间,用户不清楚网站是否有在正常加载,用户体验很差。
通过对比 FP、FCP、FMP 这三个时期 DOM 的差异,发现区别在于:
image<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
-
FP:仅有一个 div 根节点。
-
FCP:包含页面的基本框架,但没有数据内容。
-
FMP:包含页面所有元素及数据。
仍然以 Vue 为例, 在其生命周期中,mounted 对应的是 FCP,updated 对应的是 FMP。那么具体应该使用哪个生命周期的 HTML 结构呢?
image通过以上的对比,最终选择在 mounted 时触发构建时预渲染。
三 构建时预渲染方案
构建时预渲染流程:
image<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
配置读取
由于 SPA 可以由多个路由构成,需要根据业务场景决定哪些路由需要用到预渲染。因此这里的配置文件主要是用于告知编译器需要进行预渲染的路由。
在我们的系统架构里,脚手架是基于 Webpack 自研的,在此基础上可以自定义自动化构建任务和配置。
image<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
触发构建
项目中主要是使用 TypeScript,利用 TS 的装饰器,我们封装了统一的预渲染构建的钩子方法,从而只用一行代码即可完成构建时预渲染的触发。
TS的装饰器:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
使用:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
为了提高构建效率,并行对配置的多个页面或路由进行预渲染构建,保证在 5S 内即可完成构建,流程图如下:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
四 我们梳理一下简化后的项目上线过程:
开发 -> 编译 -> 上线
构建时预渲染研发流程及效果
最终,构建时预渲染研发流程如下:
image<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
开发阶段:
-
通过 TypeScript 的装饰器单行引入预渲染构建触发的方法。
-
发布前修改编译构建的配置文件。
发布阶段:
-
先进行常规的项目构建。
-
若有预渲染相关配置,则触发预渲染构建。
-
通过预渲染得到最终的文件,并完成发布上线动作。
完整的用户请求路径如下:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>
通过构建时预渲染在项目中的使用,FCP 的时间相比之前减少了 75%。
从此我们再也不受第一屏很慢的困扰了。并且可行性很好,也可以让其他有需求的一起实践。
爱前端—只专注全栈大前端技术培训,注重于企业实战型人才培养,帮助更多的零基础学员和在职前端开发人员真正掌握企业实际开发经验技术。
关注公众号【爱前端】,获取课程大纲介绍及优惠名额,领取【内部资料+试听】!
全栈大前端技术交流群:137503198(点击加入群聊),学习规划、学习路线、学习资料、问题解答,学前端,你想要的,在这都能找的到。
网友评论