当前位置: 首页 > Web前端 > HTML5

震惊老板的前端技术,KPI杀手

时间:2023-04-05 20:24:42 HTML5

随着近几年前端技术的飞速发展,越来越多的团队使用React、Vue等SPA框架作为主要技术栈。以React应用为例,从性能的角度来看,最重要的指标可能是渲染首屏所花费的时间。所以今天,我们想和大家分享一个关于极致优化的故事。我们的目标是让H5页面有Native-like的体验。如果你还在寻找一些能让你的老板震惊的技术(拯救你的KPI),那么这篇文章或许能帮到你。什么是企鹅辅导课程详情页面?企鹅家教详情页课程详情页是腾讯企鹅家教APP中最重要的页面之一,也是流量最大的页面之一,因此其打开速度也很关键。这是一个用React编写的H5页面,运行在多个终端,包括:企鹅家教APP、手机QQ、手机浏览器。架构演进纯异步渲染我们知道,目前主流SPA应用的默认渲染方式是这样的:在这种情况下,从加载页面到用户看到页面(渲染第一屏所花费的时间)在上图边框区域围起来的时间。这是最慢的方法。即使CGI足够快,也至少需要1S到2S。那么我们做一个简单的优化:把静态资源缓存起来,这样用户下次打开的时候就不需要再去网络请求了。Step④拉动CGI的动作可以提前吗?请求HTML后,我们可以先通过一个JS脚本请求CGI数据,然后在后面的步骤④中直接获取数据,这就是CGI预加载。怎么做?我们的方案是统一封装Request请求工具。使用Webpack打包时,会在页面顶部注入一个预加载的CGIJS代码,并维护一个CGI和DATA对应的MAP。后面发送请求的时候,先去MAP里面取值,有值就直接取出来,没有就发起HTTP请求。(具体可以参考我们团队开源的Preload工具)这种模式还有一些其他的优化方式:在HTML中实现Loadingstate或者skeletonscreen;移除外部CSS;使用动态polyfill;使用SplitChunksPlugin拆分公共代码;正确使用Webpack4.0的TreeShaking;使用动态导入,拆分页面代码,减少首屏JS的大小;编译为ES2015+,提高代码运行效率,减小体积;使用lazyload和placeholder来改善加载体验。效果如下图所示:同构直出的异步渲染在异步模式下,除了上面的优化之外,我们最后还做了离线包缓存(企鹅家教APP,手Q)(腾讯自主研发手机QQ)针对手机优化的解决方案,简单来说就是在手机APP中缓存静态资源)。经过我们的数据测试,首屏的渲染大概可以达到秒开(约1s)的效果。-w300但是对于对性能有着极致追求的我们来说,肯定是不会满足的。继续优化,最简单最流行的套路肯定是直出(服务端渲染)。现在解决的办法很多很多,这里就不一一介绍了。如果你想了解更多关于服务端渲染的解决方案,请参考这篇文章。StraightOut对首屏时间的优化效果非常明显。经过我们的测试,数据可以提升25%左右。直出后的效果如下图所示:直出同构可以看出,对于首屏,没有了[Loading...]的等待时间,视觉体验提升了很多。PWA直出PWA对于上述常见的直出应用,我们可以优化哪些地方呢?下面让我们详细分析一下wave,这也是我们今天要跟大家分享的重点。先看直出应用(本地环境2018iMac)各环节耗时表:进程名称process拉取CGI在NodeRenderToString20ms网络耗时10ms前端HTML渲染30ms来自上表我们可以看到,直出渲染的耗时大头还是在CGI接口的拉取中。我们现在提出两个问题:CGI接口的数据是否可以缓存?可以缓存HTML吗?1.界面的动态信息。本页面界面数据中部分数据实时变化。比如:当前剩余多少席位,此时课程的价格,用户是否购买了这门课程等等,这些数据的特性决定了这个数据接口是不能缓存的。(假设是缓存的,可能会出现这样的情况,用户进来看到还有10个名额,但是课程实际上已经卖完了。)对于这个耗时的批量,我们做了动静分离CGI接口。将与用户状态和当前时间无关的数据(如课程名称、课程时间、试听模块地址等)放在一个界面(静态界面),其他变化的数据放在另一个界面(动态界面)).然后就可以使用静态接口进行服务端渲染了。优点是第一个更快(动态信息少,后台也可以缓存),第二个可以直接用Node做缓存。2、直出Redis缓存这样我们就可以把那部分静态的、不经常变化的数据直出HTML,然后把HTML文件缓存到Redis中。客户端请求此网页。Node端收到请求后,首先去Redis中获取缓存的HTML。如果没有命中Redis缓存,它会拉取静态CGI接口来呈现HTML并将其存储在Redis中。客户端拿到HTML后,会立即渲染,然后用JS请求动态数据,渲染到相应的地方。完成后我们可以看到优化效果的提升非常非常明显:直接从262ms提升到了16ms!(当地环境),感觉像在飞,妈妈再也不用担心领队看的费时了。3.PWA直出缓存关于什么是PWA以及如何使用,请移步本文。直接从Node端做了HTML缓存之后,我们继续优化,然后想着能不能把HTML缓存在客户端,这样连网络延迟的消耗都省了。答案是在客户端使用PWA做离线缓存,在客户端缓存我们直出的HTML。每次用户请求时,直接从PWA离线缓存中取出对应的直出页面(HTML)响应用户。响应后则请求Node服务更新本地PWA缓存。(如下图所示)核心代码:self.addEventListener("fetch",event=>{//TODO其他逻辑(可能是fetchfilter)//核心逻辑event.respondWith(caches.open(cacheName).then(function(cache){returncache.match(cacheCourseUrl).then(function(response){varfetchPromise=fetch(cacheCourseUrl).then(function(networkResponse){if(networkResponse.status===200){cache.put(cacheCourseUrl,networkResponse.clone());}returnnetworkResponse;});返回响应||fetchPromise;});}));});废话不多说,先看效果对比(左边PWA直出;右边离线包):从上图duibi可以看到使用PWA直出缓存后,首屏渲染基本都在毫秒,可以说是和Native比肩了。经过我们的数据测试,使用PWA直接导出缓存,首屏渲染时间应该在400ms左右:PWA直接输出细节优化1.防跳页因为界面动静分离,所以使用静态界面直接输出这一页。然后在客户端拉取并呈现动态数据。这可能会导致页面抖动(比如详情页的试听模块在客户端渲染)。因为高度发生了变化,所以会出现视觉抖动(详见上章直出时的GIF截图)。要去除页面抖动,需要保证直出时容器的高度已经存在。比如试听模块,其实封面图和试听按钮可以在服务端渲染,而后面的Video模块则必须在客户端(腾讯云Tcplayer)渲染。所以这里可以拆分为:(试听封面+按钮+时间)服务端渲染+底层Video(客户端渲染)。一些需要在客户端计算高度的容器(一般在ComponentDidMount中计算),如果依赖于客户端环境(比如当前系统是Android还是IOS),一定不能直接在服务端渲染边。我们应该做什么?这里我们把这些计算放在HTMLbody之前,通过inlinescriptembedding来计算当前环境,在body中添加一个特定的类(class),然后在这个特定的类下的元素可以通过css赋予特定的样式。比如下面的代码:/**因为在不同的手机APP环境下,页面的padding是不一样的。*我们需要在页面渲染前添加相应的padding*/varREGEXP_FUDAO_APP=/EducationApp/;if(typeofnavigator!=="undefined"&®EXP_FUDAO_APP.test(navigator.userAgent)){if(/Android/i.test(navigator.userAgent)){document.body.classList.add("androidFudaoApp");}elseif(/iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)){if(window.screen.width===375&&window.screen.height===812){document.body.classList.add("iphoneXFudaoApp");}else{document.body.classList.add("iosFudaoApp");}}}。androidFudaoApp.tt{padding-top:48px;background-position-y:84px;}.iphoneXFudaoApp.tt{padding-top:88px;背景位置-y:124px;background-position-y:100px;}然后通过构建在页面body之前插入这段代码。-w500防抖优化效果如下(左优化,右未优化):duibi_doudong2.冷启动预加载虽然我们做了PWA离线缓存,但是对于冷启动,客户端还是没有PWA缓存。这样会导致第一次点击页面,渲染速度比较慢。这里我们可以使用一个预加载的脚本,在APP启动的时候最大程度的拉取用户可能访问的页面。核心代码如下://页面预加载时,PWA预缓存课程详情页functionprefetchCache(fetchUrl){fetch("https://youpreFetchCgi").then(data=>{returndata.json();}).then(res=>{const{courseInfo=[]}=res.result||{};courseInfo.forEach(item=>{if(item.cid){caches.open(cacheName).then(function(cache){fetch(`${courseURL}?course_id=${item.cid}`).then(function(networkResponse){if(networkResponse.status===200){cache.put(`${courseURL}?course_id=${item.cid}`,networkResponse.clone());}//返回networkResponse;});});}});}).catch(err=>{//tomonitorerr});}PWA直出遗留问题1.兼容性问题随着PWA技术的发展,现在大部分手机和PC环境都支持PWA。经过我们的测试发现:Android基本支持,IOS需要11.3以上才能支持。ServiceWorkers兼容性图2.IOS渲染问题很多经验告诉我们外部script标签应该放在body后面,因为它会阻塞页面的DOM渲染。经过测试,发现IOSWebView(UIWebView)的渲染机制和上面不太一样,而是要等到后续的JS执行才会渲染页面。如果是这样的话,我们的直出渲染优化就没有效果了(因为HTML一开始并没有),这里可以使用script标签的async和defer属性来实现异步渲染。升级WkWebView后,情况有所好转,渲染正常。附录参考资料PWA探索与最佳实践亿万访问量下的前端同构直出实践React16加载性能优化指南毫秒级的秘密-PWA直出获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云家社区公众号和知乎代理号