單頁面應(yīng)用程序(SPA)可能要花很長時間才能發(fā)射汉矿。這是一個巨大的問題肯骇,因為即使延遲一秒鐘也會使您花費7%的轉(zhuǎn)化爽篷。
但是就像您可以使單頁應(yīng)用程序?qū)EO友好一樣毡庆,您也可以提高其性能显拜。
因此吃媒,這是我們的詳細指南瓤介,針對那些希望在2020年加速SPA應(yīng)用程序的人。對于那些不熟悉SPA概念的人赘那,我建議從我們的文章開始刑桑,內(nèi)容是什么是單頁應(yīng)用程序以及它們與傳統(tǒng)多頁應(yīng)用程序的比較頁應(yīng)用程序。
監(jiān)控您的SPA效果
有很多工具可以幫助您監(jiān)視SPA性能募舟。首先漾月,您可以使用Chrome Devtools擴展,例如Lighthouse胃珍,Ember Inspector或React Performance Devtools梁肿。
此類工具的問題在于它們不能反映出您的SPA的感知加載速度(這對用戶來說很重要)。
還有諸如速度索引之類的其他工具集觅彰,它們試圖匹配實際的用戶體驗吩蔑。
但是實際用戶來自各種各樣的設(shè)備/網(wǎng)絡(luò),因此很難在綜合測試環(huán)境中考慮所有這些因素填抬。
我們建議您使用真實用戶監(jiān)視(RUM)烛芬。它被動地跟蹤人們與您的應(yīng)用程序的每次交互,從而為您提供有關(guān)用戶如何感知其加載速度的準確實時圖像。
如果您喜歡現(xiàn)成的解決方案赘娄,那么這里是一些支持單頁應(yīng)用程序的RUM工具的簡短列表:
- Dynatrace
- Catchpoint
- Akamai mPulse (former Soasta)
- Appdynamics
- Raygun Real User Monitoring
- Sematext Experience
- New Relic Browser
您還可以要求您的開發(fā)團隊將RUM應(yīng)用于SPA仆潮。
它必須識別連續(xù)導(dǎo)航的開始(又稱軟導(dǎo)航/頁面內(nèi)導(dǎo)航:應(yīng)用啟動后,用戶單擊指向SPA中新“頁面”的鏈接/按鈕)以及頁面完全被載入的那一刻遣臼。已加載性置。開發(fā)人員可以通過幾種方式實現(xiàn)此目的:
- 使用Resource Timing API識別何時進行AJAX調(diào)用以查明頁內(nèi)導(dǎo)航的開始;
- 使用Mutation Observer可以檢測到對DOM的修改揍堰,并通過Resource Timing API識別網(wǎng)絡(luò)活動的結(jié)束鹏浅。
實踐表明,這些方法可能不可靠屏歹,并且結(jié)果不準確隐砸。例如,即使用戶不啟動頁內(nèi)導(dǎo)航蝙眶,您的SPA也會調(diào)用AJAX來預(yù)取一些數(shù)據(jù)季希。否則結(jié)果可能會因網(wǎng)絡(luò)活動而歪曲,而不會影響屏幕上發(fā)生的事情幽纷。
為了解決這個問題式塌,他們可以使用簡單的API來衡量加載時間:
var rumObj = new RumTracking({
'web-ui-framework': 'EMBER'
});
// App Launch - window.performance.timing.navigationStart is the start marker
rumObj.setPageKey('feed_page_key');
// Do rendering
rumObj.appRenderComplete();
// Successive navigation
rumObj.appTransitionStart();
rumObj.setPageKey('profile_page_key');
// Do rendering
rumObj.appRenderComplete();
使用這種方法,每個頁面都必須編寫檢測代碼霹崎。
許多單頁應(yīng)用程序JavaScript框架都有生命周期hooks 珊搀,可用于自動執(zhí)行檢測冶忱。
// Add instrumentation for successive navigation start
router.on('willTransition', () => {
a. rumObj.appTransitionStart();
});
// Add instrumentation for rendering is complete
router.on('didTransition', () => {
Ember.run.scheduleOnce('afterRender', () => {
a. rumObj.appRenderComplete();
});
});
因此尾菇,開發(fā)人員可以使用Navigation Timing API的導(dǎo)航開始來檢測初始導(dǎo)航的開始。路由器的willTransition事件將標志著頁面內(nèi)導(dǎo)航的開始囚枪。
通過監(jiān)聽еруdidTransition事件并在afterRender隊列中添加工作派诬,您將知道兩種模式下頁面的完全加載時間。
現(xiàn)在链沼,您已經(jīng)擁有測量加載時間所需的一切默赂。要發(fā)現(xiàn)性能瓶頸,您需要細分RUM數(shù)據(jù)括勺。您可以通過User Timing API檢測單個資源的加載時間缆八。
現(xiàn)在是時候分析RUM數(shù)據(jù),看看可以如何解決瓶頸了疾捍。
改善SPA性能的六大方法
1.懶惰地呈現(xiàn)折疊式內(nèi)容
簡而言之奈辰,這意味著僅呈現(xiàn)頁面上當前對用戶可見的那些部分(即首屏內(nèi)容)。
如果您的SPA在渲染階段花費大量時間(請參見上圖)乱豆,則惰性渲染很有用奖恰。在此階段,應(yīng)用程序?qū)轫撁嫔系乃薪M件創(chuàng)建DOM(有關(guān)如何在頁面上表示文本,圖像和其他對象的規(guī)范)瑟啃。
一旦為首個組件構(gòu)建了DOM论泛,就可以產(chǎn)生瀏覽器的主線程。這將加快SPA的啟動速度蛹屿,因為您不必呈現(xiàn)當前不需要的資源屁奏。
將渲染優(yōu)先級分配給頁面上的所有組件可能會帶來另一個性能提升(因此瀏覽器不會同時渲染所有組件)。
這樣蜡峰,您可以加快“第一個有意義的畫圖”(即用戶看到頁面核心內(nèi)容的時間)了袁。通過不渲染某些不可見的組件,您還將改善“互動時間”湿颅。
2.使用延遲數(shù)據(jù)獲取
加快渲染階段之后载绿,您可能需要研究過渡階段。
此時油航,SPA加載數(shù)據(jù)崭庸,對其進行規(guī)范化并將其推送到存儲中。為了減少在此階段花費的時間谊囚,您需要減少SPA處理的數(shù)據(jù)量怕享。
正如您可以懶惰地呈現(xiàn)折疊式內(nèi)容一樣,您可以推遲加載數(shù)據(jù)镰踏,直到真正需要它為止函筋。
您可以使用一個高級調(diào)用來獲取“第一有意義繪畫”所需的數(shù)據(jù),而可以使用另一個調(diào)用來懶惰地加載頁面所需的其余數(shù)據(jù)奠伪。
注意:上述方法適用于啟動模式和頁內(nèi)導(dǎo)航跌帐,因為它們減少了前端時間。
一些SPA框架(例如React绊率,Angular或Vue)允許開發(fā)人員將應(yīng)用程序代碼分成幾個捆綁包谨敛。您可以同時或在必要時加載它們。第二個選項可以加快第一個導(dǎo)航的速度滤否。例如脸狸,您可以僅加載用戶可以立即訪問的部件,而推遲其他所有操作(例如藐俺,需要授權(quán)的部件)炊甲。
3.緩存靜態(tài)內(nèi)容
研究您的SPA,以確定何時可以在用戶設(shè)備上存儲圖像和其他靜態(tài)資源欲芹。
即使使用最佳服務(wù)器卿啡,從內(nèi)存或Web存儲中提取數(shù)據(jù)所需的時間也比發(fā)送HTTP請求少得多。
設(shè)備內(nèi)存比最快的網(wǎng)絡(luò)快得多耀石。因此牵囤,緩存是您最好的朋友爸黄。
對于大量集合,您可以使用某種分頁并依靠服務(wù)器來保持持久性揭鳞,或者編寫LRU算法來從存儲中擦除備用項炕贵。
您可以使用Service Worker在SPA中緩存靜態(tài)內(nèi)容。
它們是在后臺運行的客戶端腳本野崇。您可以使用它們來減少流量并啟用脫機功能称开。當瀏覽器請求內(nèi)容時,它首先經(jīng)過服務(wù)人員乓梨。如果請求的內(nèi)容存在于緩存中鳖轰,則服務(wù)工作者將檢索它并顯示在屏幕上。在其他情況下扶镀,它將從網(wǎng)絡(luò)請求資源蕴侣。
您還可以使用IndexedDB API緩存大量結(jié)構(gòu)化數(shù)據(jù)。
4.在適當?shù)牡胤绞褂肳ebSockets(即用于高度交互的應(yīng)用程序)
WebSocket是允許用戶瀏覽器和服務(wù)器之間進行雙向通信的協(xié)議臭觉。與HTTP不同昆雀,客戶端不必不斷向服務(wù)器發(fā)送請求以獲取新消息。取而代之的是蝠筑,瀏覽器僅偵聽服務(wù)器并在就緒時接收消息狞膘。
結(jié)果,您得到的連接在某些情況下可以比常規(guī)HTTP快400%什乙。
您可以使用socket.io之類的服務(wù)為SPA實現(xiàn)WebSockets挽封。
5.使用JSONP / CORS繞過同源策略
許多應(yīng)用程序的某些功能都依賴于第三方API。
但是由于同源策略臣镣,您無法對瀏覽器認為位于另一臺服務(wù)器上的頁面進行AJAX調(diào)用辅愿。要獲得對第三方API的訪問權(quán)限,該應(yīng)用將必須使用源服務(wù)器作為代理退疫。
額外的往返意味著更多的延遲渠缕。
如果不處理檢索到的數(shù)據(jù)并且不將其存儲在系統(tǒng)中鸽素,則可以直接請求資源褒繁。為此,您可以使用帶有填充的JSON(JSONP)或跨域資源共享(CORS)
JSON with Padding
JSONP充分利用了瀏覽器允許您添加來自其他域的<script>標簽的事實馍忽。通過JSONP請求棒坏,您可以動態(tài)構(gòu)造這些標簽,并將URL參數(shù)傳遞給必要的資源遭笋。
然后坝冕,標記會在JSON響應(yīng)中返回資源。
但是有一個缺點瓦呼。使用<script>標記時喂窟,JSONP僅適用于<GET>請求。您可以通過將async和defer屬性添加到外部腳本來進一步提高性能。如果沒有這些屬性磨澡,瀏覽器必須先下載并執(zhí)行腳本碗啄,然后才能顯示頁面的其余部分。
這減慢了感知的加載速度稳摄。
如果包含async屬性稚字,則瀏覽器在加載腳本時不會停止解析頁面,但是在執(zhí)行腳本時仍會暫停解析厦酬。該延遲屬性胆描,另一方面,直到頁面完全解析延遲腳本執(zhí)行
跨域資源共享
CORS允許您定義可以訪問服務(wù)器內(nèi)容的人員和方式仗阅。
但是有一個問題昌讲。使用除GET,HEAD和POST之外的任何方法的請求將啟動預(yù)檢檢查减噪,以確認服務(wù)器已準備好進行跨域請求剧蚣。
為了運行檢查,客戶端發(fā)送另一個請求旋廷,該請求描述了跨域AJAX調(diào)用的來源鸠按,方法和標頭。根據(jù)此信息饶碘,服務(wù)器決定是否處理該呼叫目尖。客戶端收到響應(yīng)后扎运,將啟動對第三方資源的請求瑟曲。
preflight檢查增加了第二次往返,因此可以有效地將您的延遲加倍豪治。
您可以使用以下方法之一來處理預(yù)檢請求:
1.編寫API并僅使用HEAD洞拨,GET,POST负拟,Accept烦衣,Accept-Language,Content-Language和Content-Type來提供內(nèi)容掩浙,因為它們不會啟動預(yù)檢請求花吟。
在接受頭使您能夠定義可接受的內(nèi)容類型的能力。默認情況下厨姚,首選類型為text / html衅澈,但是您可以將application / json或任何其他類型的內(nèi)容作為唯一的可接受類型。然后谬墙,您的后端將檢查Accept標頭今布,然后選擇發(fā)送HTML经备,JSON或其他響應(yīng)。
2.緩存預(yù)檢響應(yīng)以減少后續(xù)檢查部默。
在這種情況下弄喘,您不能依靠通常的Cache-Control標頭來定義緩存策略。但是您可以改用新的Access-Control-Max-Age標頭甩牺。其數(shù)值定義了緩存響應(yīng)所需的秒數(shù)蘑志。
6.使用內(nèi)容傳送網(wǎng)絡(luò)(CDN)
CDN是遍布全球的服務(wù)器網(wǎng)絡(luò)。您可以使用CDN以更有效的方式傳遞靜態(tài)資源(如圖像)贬派。網(wǎng)絡(luò)中的每個服務(wù)器都包含原始服務(wù)器上托管的內(nèi)容的緩存版本急但。
如果來自墨爾本的用戶請求圖像,則網(wǎng)絡(luò)將不會從位于紐約的原始服務(wù)器獲取圖像搞乏。CDN將使用澳大利亞服務(wù)器(或等待時間最少的備用服務(wù)器)來提供緩存的內(nèi)容波桩。
對單頁應(yīng)用程序使用CDN意味著更快地加載腳本并減少了交互時間。增加安全性是一個不錯的獎勵请敦。
結(jié)論說明
這些是我們加快單頁應(yīng)用程序速度的6種方法镐躲。
這是一個獎勵:測量,優(yōu)化侍筛,重復(fù)(但僅在需要時)萤皂。
此建議適用于任何應(yīng)用程序,因為優(yōu)化是一個連續(xù)的過程匣椰。
您對代碼所做的每次更改都會影響頁面加載速度裆熙。因此,請評估您的SPA的行為禽笑,并避免過早的優(yōu)化入录,因為開發(fā)人員會花費大量時間來調(diào)整其應(yīng)用程序中不必要的部分。