前端黑科技:美團網頁首幀優化實踐

前言

自JavaScript誕生以來,前端技術發展很是迅速。移動端白屏優化是前端界面體驗的一個重要優化方向,Web 前端誕生了 SSR 、CSR、預渲染等技術。在美團支付的前端技術體系裏,經過預渲染提高網頁首幀優化,從而優化了白屏問題,提高用戶體驗,並造成了最佳實踐。html

在前端渲染領域,主要有如下幾種方式可供選擇:前端

  CSR 預渲染 SSR 同構
優勢
  • 不依賴數據
  • FP 時間最快
  • 客戶端用戶體驗好
  • 內存數據共享
  • 不依賴數據
  • FCP 時間比 CSR 快
  • 客戶端用戶體驗好
  • 內存數據共享
  • SEO 友好
  • 首屏性能高,FMP 比 CSR 和預渲染快
  • SEO 友好
  • 首屏性能高,FMP 比 CSR 和預渲染快
  • 客戶端用戶體驗好
  • 內存數據共享
  • 客戶端與服務端代碼公用,開發效率高
缺點
  • SEO 不友好
  • FCP 、FMP 慢
  • SEO 不友好
  • FMP 慢
  • 客戶端數據共享成本高
  • 模板維護成本高
  • Node 容易造成性能瓶頸

經過對比,同構方案集合 CSR 與 SSR 的優勢,能夠適用於大部分業務場景。但因爲在同構的系統架構中,鏈接先後端的 Node 中間層處於核心鏈路,系統可用性的瓶頸就依賴於 Node ,一旦做爲短板的 Node 掛了,整個服務都不可用。git

結合到咱們團隊負責的支付業務場景裏,因爲支付業務追求極致的系統穩定性,服務不可用直接影響到客訴和資損,所以咱們採用瀏覽器端渲染的架構。在保證系統穩定性的前提下,還須要保障用戶體驗,因此採用了預渲染的方式。github

那麼究竟什麼是預渲染呢?什麼是 FCP/FMP 呢?咱們先從最多見的 CSR 開始提及。web

以 Vue 舉例,常見的 CSR 形式以下:chrome

一切看似很美好。然而,做爲以用戶體驗爲首要目標的咱們發現了一個體驗問題:首屏白屏問題typescript

爲何會首屏白屏

瀏覽器渲染包含 HTML 解析、DOM 樹構建、CSSOM 構建、JavaScript 解析、佈局、繪製等等,大體以下圖所示:npm

要搞清楚爲何會有白屏,就須要利用這個理論基礎來對實際項目進行具體分析。經過 DevTools 進行分析:後端

  • 等待 HTML 文檔返回,此時處於白屏狀態。
  • 對 HTML 文檔解析完成後進行首屏渲染,由於項目中對
     
    加了灰色的背景色,所以呈現出灰屏。
  • 進行文件加載、JS 解析等過程,致使界面長時間出於灰屏中。
  • 當 Vue 實例觸發了 mounted 後,界面顯示出大致框架。
  • 調用 API 獲取到時機業務數據後才能展現出最終的頁面內容。

由此得出結論,由於要等待文件加載、CSSOM 構建、JS 解析等過程,而這些過程比較耗時,致使用戶會長時間出於不可交互的首屏灰白屏狀態,從而給用戶一種網頁很「慢」的感受。那麼一個網頁太「慢」,會形成什麼影響呢?瀏覽器

「慢」的影響

Global Web Performance Matters for ecommerce的報告中指出:

  • 57%的用戶更在意網頁在3秒內是否完成加載。
  • 52%的在線用戶認爲網頁打開速度影響到他們對網站的忠實度。
  • 每慢1秒形成頁面 PV 下降11%,用戶滿意度也隨之下降下降16%。
  • 近半數移動用戶由於在10秒內仍未打開頁面從而放棄。

咱們團隊主要負責美團支付相關的業務,若是網站太慢會影響用戶的支付體驗,會形成客訴或資損。既然網站太「慢」會形成如此重要的影響,那要如何優化呢?

優化思路

User-centric Performance Metrics一文中,共提到了4個頁面渲染的關鍵指標:

基於這個理論基礎,再回過頭來看看以前項目的實際表現:

可見在 FP 的灰白屏界面停留了很長時間,用戶不清楚網站是否有在正常加載,用戶體驗不好。

試想:若是咱們能夠將 FCP 或 FMP 完整的 HTML 文檔提早到 FP 時機預渲染,用戶看到頁面框架,能感覺到頁面正在加載而不是冷冰冰的灰白屏,那麼用戶更願意等待頁面加載完成,從而下降了流失率。而且這種改觀在弱網環境下更明顯。

經過對比 FP、FCP、FMP 這三個時期 DOM 的差別,發現區別在於:



  • FP:僅有一個 div 根節點。
  • FCP:包含頁面的基本框架,但沒有數據內容。
  • FMP:包含頁面全部元素及數據。

仍然以 Vue 爲例, 在其生命週期中,mounted 對應的是 FCP,updated 對應的是 FMP。那麼具體應該使用哪一個生命週期的 HTML 結構呢?

  mounted (FCP) updated (FMP)
缺點
  • 只是視覺體驗將 FCP 提早,實際的 TTI 時間變化不大
  • 構建時須要獲取數據,編譯速度慢
  • 構建時與運行時的數據存在差別性
  • 有複雜交互的頁面,仍需等待,實際的 TTI 時間變化不大
優勢
  • 不受數據影響,編譯速度快
  • 首屏體驗好
  • 對於純展現類型的頁面,FP 與 TTI 時間近乎一致

經過以上的對比,最終選擇在 mounted 時觸發構建時預渲染。因爲咱們採用的是 CSR 的架構,沒有 Node 做爲中間層,所以要實現 DOM 內容的預渲染,就須要在項目構建編譯時完成對原始模板的更新替換。

至此,咱們明確了構建時預渲染的大致方案。

構建時預渲染方案

構建時預渲染流程:

配置讀取

因爲 SPA 能夠由多個路由構成,須要根據業務場景決定哪些路由須要用到預渲染。所以這裏的配置文件主要是用於告知編譯器須要進行預渲染的路由。

在咱們的系統架構裏,腳手架是基於 Webpack 自研的,在此基礎上能夠自定義自動化構建任務和配置。

觸發構建

項目中主要是使用 TypeScript,利用 TS 的裝飾器,咱們封裝了統一的預渲染構建的鉤子方法,從而只用一行代碼便可完成構建時預渲染的觸發。

裝飾器:

使用:

構建編譯

從流程圖上,須要在發佈機上啓動模擬的瀏覽器環境,並經過預渲染的事件鉤子獲取當前的頁面內容,生成最終的 HTML 文件。

因爲咱們在預渲染上的嘗試比較早,當時尚未 Headless Chrome 、 PuppeteerPrerender SPA Plugin等,所以在選型上使用的是 phantomjs-prebuilt(Prerender SPA Plugin 早期版本也是基於 phantomjs-prebuilt 實現的)。

經過 phantom 提供的 API 可得到當前 HTML,示例以下:

爲了提升構建效率,並行對配置的多個頁面或路由進行預渲染構建,保證在 5S 內便可完成構建,流程圖以下:

方案優化

理想很豐滿,現實很骨感。在實際投產中,構建時預渲染方案遇到了一個問題。

咱們梳理一下簡化後的項目上線過程:

開發 -> 編譯 -> 上線

假設本次修改了靜態文件中的一個 JS 文件,這個文件會經過 CDN 方式在 HTML 裏引用,那麼最終在 HTML 文檔中的引用方式是 <script src="http://cdn.com/index.js"></script>。然而因爲項目尚未上線,因此其實經過完整 URL 的方式是獲取不到這個文件的;而預渲染的構建又是在上線動做以前,因此問題就產生了:

構建時預渲染沒法正常獲取文件,致使編譯報錯

怎麼辦?

請求劫持

由於在作預渲染時,咱們使用啓動了一個模擬的瀏覽器環境,根據 phantom 提供的 API,能夠對發出的請求加以劫持,將獲取 CDN 文件的請求劫持到本地,從而在根本上解決了這個問題。示例代碼以下:

構建時預渲染研發流程及效果

最終,構建時預渲染研發流程以下:

開發階段:

  • 經過 TypeScript 的裝飾器單行引入預渲染構建觸發的方法。
  • 發佈前修改編譯構建的配置文件。

發佈階段:

  • 先進行常規的項目構建。
  • 如有預渲染相關配置,則觸發預渲染構建。
  • 經過預渲染獲得最終的文件,並完成發佈上線動做。

完整的用戶請求路徑以下:

經過構建時預渲染在項目中的使用,FCP 的時間相比以前減小了 75%。

做者簡介

寒陽,美團資深研發工程師,多年前端研發經歷,負責美團支付錢包團隊和美團支付前端基礎技術。

招聘信息

咱們美團金融服務平臺大前端研發組在高速成長中,咱們歡迎更多優秀的 Web 前端研發工程師加入,感興趣的朋友能夠將簡歷發送到郵箱:shanghanyang@meituan.com。

發現文章有錯誤、對內容有疑問,均可以關注美團技術團隊微信公衆號(meituantech),在後臺給咱們留言。咱們每週會挑選出一位熱心小夥伴,送上一份精美的小禮品。快來掃碼關注咱們吧!

相關文章
相關標籤/搜索