内容简介:关注我们作者简介
关注我们 文末有福利
作者简介
张所勇
转转平台运营中心前端负责人,在前端领域有深入研究,包括:sketch一键切图、前端数据模型化,小程序基础能力建设等多个方面,10年工作经验中,做了2年工程师,5年CEO,3年技术管理,能写点文章,也是2018年度掘金优秀作者。
细数现阶段业内首屏优化方案,主要有:SSR、Prerender、CSR等方案,这些方案的思路几乎都在于将渲染过程放到传统SPA用户端渲染之前,而传统性能优化手段在SPA项目上面收获甚微,原因在于SPA本身的致命缺陷:
SPA方案
在SPA项目的首屏性能上,我们在长期关注和不断探索,期间我们尝试过很多方案,包括:
-
从减少代码体积角度的:webpack优化、打包优化、tree-shaking等
-
从减少HTTP请求角度的:接口合并、按需加载、延时加载等各种方法减少请求
-
从缓存角度出发:离线包、http&浏览器各种缓存使用、dns预解析、dll方案、接口缓存方案等
-
从数据获取时机角度出发:webWorker预取数据、路由进入过程读取数据等
-
从减少图片体积和数量出发:使用webp图片、请求域名并行优化、CSS Sprite等
这些方案都能一定程度上降低白屏时间和首屏时间,但收效有限,很难像SSR方案一样大幅降低数据,究其原因,SPA页面渲染过程如下:
滑动查看图片
从上图可以看到,白屏过程几乎是不可避免的,因为无论如何你去优化代码体积,Vue系列类库和你需要的其他核心类库文件加起来至少有几百K,在加上这些文件执行的时间(实测至少500ms),可能大多数情况,我们白屏时间至少1200ms-1500ms了。
当然,我们可以把骨架屏所需的css放到HTML里面,能尽早的显示出骨架屏(但很多低版本内核需下载&执行完全部script后才会渲染页面),但这并非真正的首屏,即使在性能统计上,也无法直观反馈出首屏的提升。
于是SSR方案成为我们的救命稻草:
SSR方案
我们再看下SSR如何解决这个问题:
滑动查看图片
SSR方案的优势在于,浏览器下载的HTML当中已经具备了首屏渲染所需的DOM结构和样式,白屏时间几乎等于HTML文件下载时间,而这个时间相比SPA已经很少了,性能数据有显著提升。
那为什么我们不直接用SSR方案呢?
主要原因有四点:
1、SSR项目改造成本高
Vue技术栈的SSR方案主流有两种:官方方案和Nuxt.js,这两种方案相同点都是:
-
必须把现有webpack各项配置替换成上述两种方案工程
-
工程所有页面都必须SSR方式的要求实现
-
必须在自定义的asyncData/preFetch生命周期内获取数据
-
必须将接口数据使用Vuex管理
或许你认为这个也不难啊,对于一个新项目,确实不难,但对一个老项目来讲,上述的改造成本和测试成本就无比高了,这也是少有老项目改造SSR的原因。
2、SSR性能依赖接口性能
从SSR原理上你可以知道,SSR服务端渲染过程依赖于获取到全部数据才能开始渲染,一旦接口出现延时或超时,那首屏性能也会受到影响。
3、SSR负载能力和扩容能力可能成为瓶颈
几乎是业界公认,node的负载能力相比 java 等要差一些,相比nginx静态资源服务更差,并且很多公司在node服务器快速扩容上面,目前还没有太多实践和机制保障,虽然可以通过备足服务器来抵抗流量高峰,但毕竟这对应的是成本。
4、SSR无降级方案
一旦node服务故障,页面可能直接就会白屏,很多时候不是重启服务能够解决的,毕竟SSR不是像SPA一样在浏览器看见什么错误去解决或者回滚就可以的,你必须真正解决了故障才能恢复服务,这期间不能很容易的降级为SPA方案。
上述原因当中,最主要阻碍我们用SSR的原因是改造成本。
Prerender方案
Prerender是基于prerender-spa-plugin这个webpack插件实现的,原理如下:
滑动查看图片
核心原理就是在webpack打包过程中,通过Puppeteer访问对应路由,抓取html并静态化,再部署cdn。
但业内这种方案使用的比较少,主要原因有:
-
静态化过程发生在构建环节,用户访问时看到的数据注定是过时的。
-
这种方案依赖于使用history方式的路由,这对老项目的改造测试成本也不低。
-
编译时间大幅增加,想想就知道啦。
通过上述分析,我们能看出“最优方案”应该是SSR,不考虑负载能力的话,阻碍我们的只有改造成本了,能否用较低的成本实现跟SSR一样的效果呢?
离线预渲染OPR
晴空一声惊雷,OPR产生了,我们把他命名为离线预渲染OPR(Offline Prerender)。
OPR的渲染过程:
滑动查看图片
不同于SSR在用户访问阶段的渲染,OPR是一个独立于用户访问流程的渲染服务,它通过Puppeteer定期渲染页面并上传cdn,用户访问到的页面将会是纯静态页面,可以说是结合了SSR和Prerender两种方案。
与SSR方案的区别:
-
渲染过程独立于用户访问,没有服务器压力,占用资源极小,一台服务器即可完成
-
页面几乎不需要任何改动
-
渲染出来的页面效果几乎和SSR一致
-
可降级为SPA方案
与Prerender方案的区别:
-
通过定时渲染,解决Prerender方案数据无法及时更新的问题
-
页面几乎不需要任何改动
-
对原本项目构架过程无任何影响
OPR方案实现过程
我们简单拆解来看:
01
定时访问页面
我们首先搭建一个node服务,通过schedule机制定期通过Puppeteer访问需要渲染的页面。
02
等待页面渲染
页面渲染是一个动态的过程,我们如何知道页面已经渲染完了呢,Puppeteer其实提供多种方案,但我们最终选用的方案是通过监听公司性能统计埋点发出时机,通过Puppeteer的page.waitForRequest方法可以很容易实现。
03
抓取HTML
你必须清楚一点:我们抓取的是浏览器渲染的HTML,并非你请求到index.html文件内容。
前者你可以理解为,通过浏览器开发者工具,选中html标签,右键拷贝outerHTML。
后者你可以通过浏览器查看下html源码,里面应该只有空白的dom和一些