基于Vue-SSR优化方案归纳总结
CSR與SSR的區(qū)別首先,還是要不厭其煩地過一遍CSR和SSR的區(qū)別,在理清整個(gè)流程后,才能發(fā)現(xiàn)性能瓶頸以及關(guān)鍵耗時(shí)在哪里。
CSR一般由靜態(tài)資源服務(wù)器(CDN)等直接返回HTML資源,之后瀏覽器解析HTML加載CSS、JS資源(CSS加載結(jié)束后頁(yè)面會(huì)盡快進(jìn)行首屏渲染FP),JS依賴加載結(jié)束后,Vue實(shí)例初始化,拉取頁(yè)面數(shù)據(jù),頁(yè)面渲染(FMP)。
SSR由nodejs服務(wù)器來(lái)直出頁(yè)面,請(qǐng)求到達(dá)后端后,后端拉取cgi接口數(shù)據(jù),根據(jù)直出bundle生成render對(duì)象,render對(duì)象將執(zhí)行客戶端代碼構(gòu)建VDOM,生成HTML string,填充進(jìn)模板HTML,返回HTML資源,瀏覽器解析后加載CSS、JS資源,(在CSS加載結(jié)束后觸發(fā)FP和FMP),Vue實(shí)例初始化,接管后端直出的HTML,頁(yè)面可響應(yīng)。
(以下流程圖引自:https://www.jianshu.com/p/10b6074d772c)
時(shí)序圖(注:FP即First paint,首屏渲染,可能是沒有數(shù)據(jù)的狀態(tài)。FMP即First meaningful paint,處于已經(jīng)渲染數(shù)據(jù)的狀態(tài)。可交互:頁(yè)面數(shù)據(jù)填充結(jié)束且可響應(yīng)。)SSR存在的缺陷:
1、對(duì)服務(wù)器提出更高的要求,生成虛擬DOM如果相對(duì)較長(zhǎng)的運(yùn)行和計(jì)算耗時(shí);2、由于cgi拉取和vdom直出后才吐出HTML頁(yè)面,FMP雖然提前了,但是FP相對(duì)延遲了;3、相比CSR,SSR渲染后,由于仍然需要進(jìn)行依賴、vue初始化,頁(yè)面可交互時(shí)間并沒有較大改善。常見優(yōu)化方法雖然SSR仍有許多不足之處,但是也不是沒有改善的空間。
一、緩存優(yōu)化
1、頁(yè)面級(jí)別緩存:vuessr官網(wǎng)給我們提供了一種方法,如果頁(yè)面并非千人千面,總是為所有用戶渲染相同的內(nèi)容,我們可以利用名為 micro-caching 的緩存策略,來(lái)大幅度提高應(yīng)用程序處理高流量的能力。這通常在 Nginx 層完成,也可以在 Node.js 中實(shí)現(xiàn)。
2、組件級(jí)別緩存:通過對(duì)組件設(shè)置serverCacheKey的方式,如果組件serverCacheKey相同,將復(fù)用之前渲染的組件產(chǎn)物,不需要重新渲染。具體是類似這樣的:export default {name: 'myComponent', // 必填選項(xiàng)
props: ['item'],
serverCacheKey: props => props.item.id,
render (h) {
return h('div', this.item.id)
}
}3、cgi接口緩存:如果部分cgi接口返回的數(shù)據(jù)是固定的, 我們可以在node后端拉取cgi的時(shí)候,設(shè)置cgi緩存,緩存至memcache或其他輕量存儲(chǔ)服務(wù),當(dāng)然,你也需要設(shè)置好緩存更新策略。
二、代碼實(shí)現(xiàn)優(yōu)化
1、減少組件嵌套層次,優(yōu)化HTML結(jié)構(gòu):由于組件最初需要在node后端進(jìn)行VDOM計(jì)算和渲染,優(yōu)化組件層次結(jié)構(gòu),減少過深曾經(jīng)的DOM嵌套,可以減少VDOM計(jì)算耗時(shí)。
2、減少首頁(yè)渲染數(shù)據(jù)量:根據(jù)業(yè)務(wù)調(diào)整用戶首屏可見的所需渲染的數(shù)據(jù),其他數(shù)據(jù)懶加載或異步加載。
三、資源加載
1、流式傳輸:vuessr官網(wǎng)給我們介紹了一種方法,render對(duì)象會(huì)暴露renderToStream方法,把原有的直出結(jié)果以流的形式輸出,讓我們可以更快的響應(yīng)數(shù)據(jù)到客戶端,能減少首屏渲染時(shí)間,更早開始加載頁(yè)面資源。(流式傳輸需要在asyncData執(zhí)行結(jié)束后開始,否則沒有數(shù)據(jù),這意味著流失傳輸受限于cgi拉取耗時(shí))
2、分塊傳輸:lucien大佬在tweb大會(huì)上給我們帶來(lái)了新的思路,由模板的語(yǔ)法樹, 分析代碼的上下文,分析數(shù)據(jù)和模板間的依賴,用異步數(shù)據(jù)分割模板,分塊逐步輸出。(相比流式傳輸,前置位的cgi數(shù)據(jù)一旦ready,就會(huì)渲染輸出,而不需要等待所有的gi拉取到后才開始渲染輸出,但是該方案改造成本較大)
一張圖說(shuō)明白這兩者的區(qū)別:四、改造SSR算法
SSR算法改造:在tweb大會(huì)上lucien給我們介紹了一個(gè)新的思路,改造直出算法,不用vue-loader而用自研的aga-loader,將vdom渲染轉(zhuǎn)換為字符串模板,具有更高的渲染性能。
性能提高的同時(shí),由于沒有完整的組件運(yùn)行環(huán)境,也帶來(lái)了部分語(yǔ)法上的約束,同時(shí),也不支持vuex。
思考
看到這里,讀者們應(yīng)該對(duì)SSR了如如來(lái)神掌且熟悉了常見的優(yōu)化方法,但是回頭思考一下,Vue-SSR的優(yōu)化無(wú)非是在 cgi拉取 和 VDOM直出渲染 上下功夫,因?yàn)檫@兩者就是node后端最耗時(shí)的步驟,其次,由于這種耗時(shí)會(huì)同步阻塞頁(yè)面的FP,所以更進(jìn)一步的方法是流式輸出或分塊,減少首屏渲染時(shí)間。
然而,但是并不是所有的cgi都能緩存,類似拉取用戶個(gè)人信息的cgi就無(wú)法緩存,SSR算法改造成本大,約束也大。再看看流式傳輸和分塊傳輸,兩者雖然都對(duì)FP時(shí)間優(yōu)化了,但流式傳輸受限于cgi拉取時(shí)間,分塊傳輸改造成本大。而且兩者存在的一個(gè)共性問題,那就是可交互時(shí)間仍然沒有優(yōu)化。
當(dāng)然,這里并不是要否定所有的優(yōu)化方法,而是方法各有優(yōu)劣,比較優(yōu)缺點(diǎn)大家才能根據(jù)自己的業(yè)務(wù)需求和優(yōu)化場(chǎng)景選取合適的優(yōu)化方法。受流式傳輸和分塊傳輸?shù)膯l(fā),我們能不能在這上面下功夫?在請(qǐng)求到來(lái)時(shí),先返回一份完整的HTML空頁(yè)面,讓客戶端更快的FP,其次,后端拉取cgi和渲染VDOM 與?前端拉取CSS、JS資源?兩者同步進(jìn)行,之后再吐出直出的HTML string 與 頁(yè)面store,再次渲染頁(yè)面,這樣的話FP提前了,和CSR的FP時(shí)間一毛一樣,其次,FMP相比CSR大大提高,更重要的是,由于JS資源的加載讓Vue初始化觸發(fā)的更早,意味著頁(yè)面可響應(yīng)時(shí)間也會(huì)提高。
為了闡明這種區(qū)別,我們看一下流程圖:新方案探索與實(shí)踐
先吐空頁(yè)面,之后再吐直出后的數(shù)據(jù),但是關(guān)鍵是怎么讓直出后的數(shù)據(jù)再渲染上去,同時(shí)不要讓JS先執(zhí)行了,導(dǎo)致頁(yè)面直接變成CSR了。
思考?xì)v程:不要讓JS執(zhí)行,等直出數(shù)據(jù)回來(lái)了再執(zhí)行,這可咋辦,筆者最初想實(shí)現(xiàn)一個(gè)JS加載控制器,不通過script來(lái)引入js,而是自己去拉取js代碼,eval函數(shù)執(zhí)行,這樣js的執(zhí)行控制權(quán)就在自己手上了,但是有幾個(gè)問題,eval函數(shù)解析只是把字符串當(dāng)js來(lái)執(zhí)行,那錯(cuò)誤上報(bào)就會(huì)出問題,接了sentry錯(cuò)誤上報(bào)是基于js文件、錯(cuò)誤行列來(lái)定位的,除此之外,ajax來(lái)拉取js代碼會(huì)不會(huì)存在性能問題,和瀏覽器加載js資源速度上是否存在差異?還有第三方j(luò)s不能直接ajax拉取,需要設(shè)置跨域頭。于是筆者開始換一種思路,能不能給每個(gè)js文件包裹一層函數(shù),通過setTimeout(fn,0)的方式來(lái)延遲調(diào)用,但是這又有問題,有多個(gè)js文件且文件已經(jīng)是打包好了的,改了js文件,map映射不就亂了嗎?錯(cuò)誤上報(bào)不就亂了嗎?
源碼在自己手里,為啥不直接在源碼上提供一個(gè)調(diào)用入口,來(lái)觸發(fā)js執(zhí)行,最后直出的時(shí)候吐出<script>window.render()<script>來(lái)控制js執(zhí)行不就可以了嗎?
開始改造
客戶端改造:
原有的直出存在entry-client和entry-server兩個(gè)js文件,分開兩個(gè)入口各自打包,我們需要改造的是entry-client,讓其可控制,開頭筆者只是對(duì)new App()和mount 包裹了一層函數(shù),但是后來(lái)發(fā)現(xiàn),第三方j(luò)s依賴執(zhí)行了,其實(shí)如果你明白webpack的打包原理,那么require的時(shí)候就會(huì)觸發(fā)相應(yīng)的依賴執(zhí)行,我們要在entry-client之外再包一層來(lái)控制。新增entry-runner文件:window.__GLOBAL_RENDER__ = function() {require("./entry-client.js");
console.log("__GLOBAL_RENDER__ ENDING"); // eslint-disable-line
};
以這個(gè)為打包入口,加載完js后就不會(huì)執(zhí)行了(當(dāng)然,還會(huì)執(zhí)行webpack的運(yùn)行時(shí)代碼,控制chunk執(zhí)行的,忽略不計(jì))
改造服務(wù)端直出代碼:module.exports = functionhandle(req,res){res.writeHead(200, {
'Content-Type': 'text/html',
});
res.write(FP_html.replace(/<\/body>[\s\S]*<\/html>/g,''));
// ...other code
}
FP_html是客戶端打包的時(shí)候生成的index.html,里面已經(jīng)插入好了css、js依賴,你只需要把尾部body和html的結(jié)束標(biāo)簽去掉。
接下來(lái)是在直出后吐出直出數(shù)據(jù)。constcontext = {url: req.REQUEST.pathname
};
consthtml = awaitrenderToString(context);
consthtml_render = html.match(/(<div data-server-rendered[\s\S]*<\/div>)[\s\S]*(<script>window.__INITIAL_STATE__[\s\S]*injected -->)/g);
constinnerHTML = RegExp.$1;
conststate = RegExp.$2;
res.write(`
<script>
document.body.innerHTML = \`${innerHTML}\`
</script>
${state}
<script>
setTimeout(()=>{
window.__GLOBAL_RENDER__ && window.__GLOBAL_RENDER__()
},0)
</script>
</body>
</html>
`);
res.end();
通過正則提取出渲染結(jié)果html以及store,之后write吐會(huì)給前端,innerHTML會(huì)覆蓋第一次吐給前端的頁(yè)面中的div#app,接下來(lái)state即全局的store初始化,最后setTimeout控制window.__GLOBAL_RENDER__執(zhí)行即可,因?yàn)関ue判斷是否直出是根據(jù)div以及全局的store是否初始化來(lái)判斷的,所以我們這樣做沒有問題。其次,為了優(yōu)先觸發(fā)一次FMP,我們需要通過setTimeout的方式調(diào)用全局渲染方法。
接下來(lái)我們來(lái)比較一下CSR、SSR以及改造后的效果:
CSR:SSR:優(yōu)化后的SSR:各項(xiàng)數(shù)據(jù)對(duì)比:| 類型\指標(biāo) | FP | FMP | 可交互時(shí)間 |
| CSR | 0.4s | 1.1s | 1.2s |
| SSR | 0.7s | 0.7s | 1s |
| 優(yōu)化SSR | 0.4s | 0.6-0.7s | 0.8s |
可以明顯的看到,優(yōu)化后的SSR,FP時(shí)間跟CSR一樣,讓我們的首屏渲染更快了(可優(yōu)先渲染頁(yè)面骨架圖),其次,FMP時(shí)間跟SSR相差不大,最后是可交互時(shí)間,由于JS依賴較早開始加載,所以頁(yè)面直出結(jié)束后可馬上執(zhí)行vue初始化邏輯,所以可交互時(shí)間縮短到0.8s。
我們找到了一種成本不是很高,不僅優(yōu)化了FP、FMP時(shí)間還優(yōu)化了可交互時(shí)間的方法!
最后文章看到了這里,相信你對(duì)Vue-SSR有了更加深刻的認(rèn)識(shí)和了解,本文比較了CSR和SSR,并總結(jié)歸納了Vue-SSR的常見方法,最后在新的方案上進(jìn)行嘗試,達(dá)到了一定程度上的優(yōu)化。優(yōu)化方案各有優(yōu)劣,也有成本開銷,根據(jù)自己業(yè)務(wù)需求來(lái)選擇合適的優(yōu)化方法,才是最有效的。希望本文能給你帶來(lái)幫助~ 也歡迎討論其他方法~
總結(jié)
以上是生活随笔為你收集整理的基于Vue-SSR优化方案归纳总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 化繁为简 - 腾讯计费高一致TDXA的实
- 下一篇: 升级 Vue3 大幅提升开发运行效率