Vue-SSR相信大家都不陌生了。与传统SPA相比,服务端渲染(SSR)可以有更好的SEO,方便搜索引擎爬虫直接查看。渲染页面,另外,SSR可以在更短的时间内渲染页面内容,并且通过在服务端填充数据,吐出到客户端,让用户有更好的用户体验。Preface基于VueSSR的页面优化很常见,但是针对VueSSR重新优化并不常见。前段时间有幸作为无敌宇宙特工参加了前端tweb大会,听了腾讯视频网高级工程师lucien分享了一些针对SSR场景的优化。由于笔者之前在项目中也实现过SSR渲染,所以也是针对Vue的。-对SSR的优化进行了实践和总结,在前人的基础上进行了新的优化尝试。当然,在不同的项目和场景下,优化效果和优化方案可能会有所不同,需要读者自行选择~(本文将讨论常见的SSR优化方案和作者个人的优化尝试)CSR和SSR的区别一、CSR和SSR的区别还是有必要费点功夫的。弄清楚整个过程,就可以找到性能瓶颈和关键消耗的时间和地点。CSR一般是从静态资源服务器(CDN)等直接返回HTML资源,然后浏览器解析HTML并加载CSS和JS资源(CSS加载完成后页面会第一时间在首屏渲染FP)).JS依赖加载完成后,初始化Vue实例。拉取页面数据,页面渲染(FMP)。SSR是直接从nodejs服务器发送到页面的。请求到达后端后,后端拉取cgi接口数据,根据直出bundle生成render对象。渲染对象将执行客户端代码来构建VDOM,生成HTML字符串,并将其填充到模板HTML中。返回HTML资源,浏览器解析后加载CSS和JS资源,(CSS加载完成后触发FP和FMP),初始化Vue实例,接管后端直接输出的HTML,以及页面可以响应。(以下流程图引用自:https://www.jianshu.com/p/10b...)时序图(注:FP是Firstpaint,首屏渲染可能是没有数据的状态。FMP是First有意义的paint,处于已经渲染数据的状态。Interactive:页面数据已填充,可以响应。)SSR的缺陷:1.对服务器提出了更高的要求,如果虚拟DOM生成比较长,计算时间比较长;2、由于cgi拉取vdom直出,HTML页面吐出,虽然FMP提前,但是FP相对延迟;3、与CSR相比,SSR渲染后,由于仍然需要依赖和Vue初始化,页面的交互时间并没有太大的提升。常见的优化方法虽然SSR还有很多不足,但也不是没有改进的余地。一、缓存优化1、页面级缓存:vuessr官网为我们提供了一种方法。如果页面不是几千人,总是为所有用户呈现相同的内容,我们可以使用一种称为微缓存的缓存策略。大大提高应用程序处理高流量的能力。这通常在Nginx层完成,但也可以在Node.js中实现。2、组件级缓存:通过为组件设置serverCacheKey,如果组件serverCacheKey相同,则之前渲染的组件产品将被复用,无需重新渲染。具体来说,它看起来像这样:,this.item.id)}}3.cgi接口缓存:如果cgi接口返回的部分数据是固定的,我们可以在node后端拉取cgi的时候设置cgi缓存,缓存在memcache或者其他轻量级storage服务,当然还需要设置缓存更新策略。2、代码实现优化1、减少组件嵌套层数,优化HTML结构:由于组件最初需要在节点后台进行VDOM计算和渲染,优化组件层级,减少过深的DOM嵌套,可以减少VDOM计算耗时。2.减少首页渲染数据量:根据业务调整需要渲染在用户首屏可见的数据,其他数据懒加载或异步加载。三、资源加载1、流式传输:vuessr官网给我们介绍了一种方法。render对象会暴露renderToStream方法,将原本直接输出的结果以流的形式输出,这样我们可以更快的响应数据给客户,可以减少首屏的渲染时间,更早的开始加载页面资源。(流式传输需要在asyncData执行结束后才开始,否则会没有数据,也就是说损失传输受限于cgipull的耗时)2.Chunk传输:lucien在tweb会议,从模板的语法树出发,分析代码的上下文,分析数据与模板的依赖关系,拆分带有异步数据的模板,分步输出。(相对于streaming,一旦前置的cgi数据准备好,就会进行渲染输出,而不是等所有的gi拉完才开始渲染输出,但是这种方案的改造成本比较高)一张图展示了这两者的区别:4.SSR算法的改造1.SSR算法的改造:在tweb大会上,lucien给我们介绍了一个新思路,改造直出算法,使用自研的aga-loader代替vue-loader,并将vdom渲染转换为字符串模板,渲染性能更高。在性能提升的同时,由于缺乏完整的组件运行环境,在语法上存在一些限制。同时不支持vuex。想了想,读者应该对SSR不陌生,熟悉常见的优化方式,但是回过头来看,Vue-SSR的优化无非就是在cgi拉取和VDOM直出渲染上下功夫,因为这两个是后者是节点后端最耗时的步骤。其次,因为这个耗时会同步阻塞页面的FP,进一步的方法是流式输出或者阻塞来减少首屏渲染时间。但是,并非所有CGI都可以缓存。例如,无法缓存拉取用户个人信息的CGI。SSR算法改造成本高,约束条件大。再看流式传输和块式传输,虽然都是针对FP时间进行了优化,但是流式传输受限于cgipull时间,块式传输改造成本高。并且两者有一个共同的问题,就是交互时间依然没有优化。当然,这并不是否定所有的优化方法,只是每种方法都有自己的优缺点。只有比较优缺点,才能根据自己的业务需求和优化场景选择合适的优化方式。受流和分块的启发,我们可以解决这个问题吗?当请求来的时候,先返回一个完整的空HTML页面,这样客户端可以实现更快的FP。其次,后端拉取cgi渲染VDOM和前端同时拉取CSS和JS资源,然后吐出直接输出的HTML字符串和pagestore再次渲染页面。这样,FP就提前了,和CSR的FP时间是一样的。其次,FMP相比CSR有很大的提升。更重要的是,由于JS资源的加载,Vue初始化触发更早,意味着页面响应时间也会提升。(我的天!)为了搞清楚这个区别,我们先看一下流程图:新解探索实践先清空页面,再吐出直出数据,关键是如何渲染直出-outdata上去,不要让JS先执行,导致页面直接变成CSR。思考过程(可跳过的碎碎念):不让js执行,等数据回来再执行,怎么办,作者本来想实现一个js加载控制器,而不是通过脚本导入js,而是Pull自己编写js代码,并执行eval函数,这样js的执行控制权就掌握在自己手中,但是存在几个问题。eval函数分析只是将string作为js执行,然后报错的时候就会有问题。收到哨兵后,根据js文件和错误排名报错。另外,通过ajax拉取js代码会不会有性能问题,浏览器加载js资源的速度有区别吗?还有就是第三方js不能直接ajax拉取,需要设置跨域header。于是作者开始换个思路,是否可以为每个js文件包裹一层函数,通过setTimeout(fn,0)延迟调用,但是这样就有问题了,js文件有多个,而且文件已经打包好了,如果改了js文件,map映射不就乱了吗?报错不乱吗?哈哈哈哈源码在自己手里,何不直接在源码上提供一个调用入口触发js的执行,最后吐出来控制js的执行哈哈哈哈,之前的想法真的很蠢*...开始改造client端:原来直接输出有两个js文件,entry-client和entry-server,两个entry分开打包。我们需要改造的是入口-客户端,让它可控。一开始笔者只是为newApp()和mount包裹了一层函数,后来发现执行了第三方js依赖。其实如果了解webpack的打包原理,那么require会触发相应的依赖执行,我们需要在entry-client之外再包裹一层来控制。添加入口运行文件:window.__GLOBAL_RENDER__=function(){require("./entry-client.js");console.log("__GLOBAL_RENDER__ENDING");//eslint-disable-line};以此作为包入口加载js后不会执行(当然也会执行webpack的runtime代码来控制chunk的执行,可以忽略不计)改造服务端直接导出代码:module.exports=functionhandle(req,res){res.writeHead(200,{'Content-Type':'text/html',});res.write(FP_html.replace(/<\/body>[\s\S]*<\/html>/g,''));//...其他代码}FP_html是客户端打包时生成的index.html,里面已经插入了css和js依赖。你只需要去掉最后的body和html的结束标签。接下来是直出后吐出直出数据。constcontext={url:req.REQUEST.pathname};consthtml=awaitrenderToString(context);consthtml_render=html.match(/(
