Web優化躬行記(6)——優化閉環實踐

2022-08-08 09:01:44

  在遇到一個頁面效能問題時,我理解的優化閉環是:分析、策略、驗證和沉澱。

  • 分析需要有分析資料,因此得有一個效能監控管理。
  • 策略就是制訂針對性的優化方案,解決當前遇到的問題。
  • 驗證的物件上述策略,判斷方案是否有效,同樣需要資料支撐。
  • 沉澱就是將解決過程檔案化、通用化,能夠總結成一套實際方案、優化規則等。

  這其中非常關鍵的一步是需要採集到效能資料,並且得有個視覺化後臺檢視資料變化。

  在之前已經自制了一個效能優化平臺,採集前端效能引數的 SDK 叫 shin.js

一、優化的三部分

  在文章開頭,我想先聊聊網頁優化的三部分:網路,渲染和容器。

  第一部分的網路就是提升傳輸速度,可優化的手段包括 gzipped壓縮、CDN、HTTP 快取、HTTP 2.0協定、並行請求等。

  像 HTTP 快取分為強快取和協商快取,請求首部和瀏覽器配合完成資源的快取機制,下圖摘自《前端程式設計師面試筆試寶典》。

  

  第二部分的渲染就是 CRP 優化(關鍵渲染路徑),CRP 是指瀏覽器從接收資源到渲染畫素的過程。

  優化的點包括資源數、位元組數和載入時序。現代化的 webpack 構建工具就會對資源做前兩項的優化處理,包括壓縮檔案、合併檔案、優化包的引入等。

  載入時序就包括日常都會用的圖片懶載入和預載入、指令碼的延遲(defer)、非同步(async)和預載入(preload)等。

  對於比較龐大的首頁,可以先將那些能阻塞網頁首次渲染的關鍵資源載入,其餘資源都延遲載入,以此提升頁面開啟速度。

  第三部分的容器(WebView)就是借用端的能力,讓 APP 配合優化網頁。

  例如預請求,將請求介面的時機前置到容器開啟之時,下面是一張實現流程圖。

  

  還有一種靜態資源快取至使用者端本地,當時與公司使用者端討論此方案時,他們覺得每次攔截請求會損傷效能,後面就採用了折中的辦法。

  就是他們去主動請求特定地址的靜態資源,然後開放介面讓我可以去讀取本地資源,也就是說由 Web 來控制是否讀取快取資源。

二、問題引出  

  現在言歸正傳,回到本次的優化中來。

  為了提升頁面產出率,聯合 UI 設計構建了一套可設定的通用活動模板

  活動上線後,就檢視了效能資料,情況很不理想,如下圖所示。

  

  FP(白屏)時間大部分都在 2 秒以上,取平均值更是在 3 秒左右。Google的報告指出:

  • 如果網頁載入時間從 1 秒增加到 3 秒,跳出率就會提高 32%
  • 如果網頁載入時間從 1 秒增加到 6 秒,跳出率就會上升 106%

三、資料排查

  在資料庫中,將指定的效能資料記錄匯出到 Excel 中。

  翻了一條後發現,效能問題集中在 DOM 中。

{
    "unloadEventTime": 0,
    "loadEventTime": 1,
    "interactiveTime": 1255,
    "parseDomTime": 1075,
    "initDomTreeTime": 721,
    "readyStart": 5,
    "redirectCount": 0,
    "compression": 0,
    "redirectTime": 0,
    "appcacheTime": 0,
    "lookupDomainTime": 0,
    "connectSslTime": 0,
    "connectTime": 0,
    "requestTime": 119,
    "requestDocumentTime": 119,
    "responseDocumentTime": 0,
    "TTFB": 534,
}

  JSON 中的 interactiveTime、parseDomTime 和 initDomTreeTime 消耗的時間都不短,計算規則如下所示。

    /**
     * 解析 DOM 樹結構的時間
     * 期間要載入內嵌資源
     * 反省下你的 DOM 樹巢狀是不是太多了
     */
    api.parseDomTime = timing.domComplete - timing.domInteractive;
    /**
     * 請求完畢至DOM載入耗時
     */
    api.initDomTreeTime = timing.domInteractive - timing.responseEnd;
    /**
     * 首次可互動時間
     */
    api.interactiveTime = timing.domInteractive - timing.fetchStart;

  參考 W3C 第二版效能引數圖可知,慢的地方集中在 Processing 階段。

  

四、Chrome DevTools

  開啟 Chrome DevTools 中的 Performance 一欄,錄製後,可在火焰圖中看到長任務。

  點選 Long task 連結,會跳轉到使用 RAIL 模型衡量效能一文。

  

  在 PC 瀏覽器中開啟肯定會比在手機中快,但即使如此,還是出現了效能瓶頸,說明這裡是真的慢。

  藍底的 DCL 是 DOMContentLoaded 事件,在 HTML 檔案被完全載入和解析後觸發,綠底的 FP 就是白屏時間。

  黃底的 Evaluate Script 表示載入 JavaScript 指令碼,Compile Script 表示執行 JavaScript 指令碼。

  再來看看網路請求瀑布圖,下圖中的藍線就是 DCL,可以清晰的看到,藍線之前在載入的基本都是 JavaScript 指令碼。

  

  由此可知,載入的指令碼有點多,並且有一個 chunk-vendors.js 指令碼還比較大,下載時間有點長(依據藍色塊)。

五、程式碼分析

  定位到了問題根源,那就直接檢視基於 Vue 的程式碼是怎麼寫的了。

1)HTML

  下面是編譯後的頁面 HTML 結構,只列出了關鍵部分。

<!DOCTYPE html>
<html lang=en>
<head>
  <script src=https://res.wx.qq.com/open/js/jweixin-1.6.0.js></script>
  <script src=//www.xxxx.com/flexible/flexible.js></script>
  <script src=//www.xxxx.co/files/js/baidu.js></script>
  <script src=//www.xxxx.co/files/js/shin.js></script>
  <link href=//www.xxxx.me/game/css/operation37.cba04f10.css rel=preload as=style>
  <link href=//www.xxxx.me/game/js/chunk-lodash.152ef24b.js rel=preload as=script>
  <link href=//www.xxxx.me/game/js/chunk-lottie.23b9982e.js rel=preload as=script>
  <link href=//www.xxxx.me/game/js/operation37.fa5f5378.js rel=preload as=script>
  <link href=//www.xxxx.me/game/css/chunk-vendors.779f7d1d.css rel=stylesheet>
  <link href=//www.xxxx.me/game/css/operation37.cba04f10.css rel=stylesheet>
</head>

<body>
  <div id=app></div>
  <script src=//www.xxxx.me/game/js/chunk-vendors.ca022e99.js></script>
  <script src=//www.xxxx.me/game/js/operation37.fa5f5378.js></script>
</body>
</html>

  首先在 head 中,引入了大量的 JavaScript 指令碼,flexible.js 其實在構建時可以內聯,不需要網路存取。

  然後 jweixin-1.6.0.js 和 baidu.js 這兩個指令碼完全可以延遲載入,後者就是增加百度統計的指令碼。

  接著就是 shin.js 需要做壓縮處理,可以減少 50% 以上的尺寸。

  在 link 元素中,使用了 preload,表示可並行的預載入,並且不會執行,這是提升頁面效能的一種手段。

  雖然第三方的庫(chunk-vendors.ca022e99.js)和業務主邏輯(operation37.fa5f5378.js)兩個指令碼宣告在 body 中。

  但是主結構就是個空的 div,因此在載入和執行時就會延長 DOM 的解析,影響白屏時間。

2)vendors 優化

  Vue 內建了一條命令,可以檢視每個指令碼的尺寸以及內部依賴包的尺寸。

  在下圖中,vendors.js 的原始尺寸是 3.76M,gzipped 壓縮後的尺寸是 442.02KB,比較大的包是 lottie、swiper、moment、lodash 等。

  

  這類比較大的包可以再單獨剝離,不用全部聚合在 vendors.js 中。

  在 vue.config.js 中,設定 config.optimization.splitChunks(),如下所示,引數含義可參考官網

config.optimization.splitChunks(
      {
        cacheGroups: {
          vendors: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            chunks: 'initial'
          },
          lottie: {
            name: 'chunk-lottie',
            test: /[\\/]node_modules[\\/]lottie-web[\\/]/,
            chunks: 'all',
            priority: 3,
            reuseExistingChunk: true,
            enforce: true
          },
          swiper: {
            name: 'chunk-swiper',
            test: /[\\/]node_modules[\\/][email protected]@swiper[\\/]/,
            chunks: 'all',
            priority: 3,
            reuseExistingChunk: true,
            enforce: true
          },
          lodash: {
            name: 'chunk-lodash',
            test: /[\\/]node_modules[\\/]lodash[\\/]/,
            chunks: 'all',
            priority: 3,
            reuseExistingChunk: true,
            enforce: true
          }
        }
      }
    )

  在經過一頓初步操作後,原始尺寸降到 2.4M,gzipped 壓縮後的尺寸是 308.64KB,比之前少了 100 多 KB。

  

  現在在入口處需要單獨宣告依賴的包,否則不會自動引入。

pages: {
   operation37: {
      entry: 'src/pages/operation37/index.js',
      template: 'src/pages/operation37/index.html',
      filename: 'operation37.html',
      title: '榜單設定頁面',
      chunks: ['chunk-lottie', 'operation37', 'chunk-vendors']
   },
}

  其實大部分的 H5 頁面都比較簡單,可能就使用了包的一個小功能,那完全可以自己用程式碼實現,這樣就不必依賴那個大包了。

  後面就是在程式碼邏輯層面的優化,核心就是減少指令碼尺寸。優化後,再去觀察資料的變化。

3)CDN加速

  之前部分靜態資源採取了 CDN 加速,現在需要將 game 下面中的靜態資源全部走 CDN。

  在雲端設定些引數,就能走 CDN。不過,第一次沒有設定好,沒有設定轉發路徑,造成了嚴重的線上問題。

  第二次就比較謹慎,在測試環境將之前碰到的問題都驗證後,才最終線上上設定。

  白屏時間佔比變化:

  • 1 秒內的佔比從 77.3% 最高提升至 78.7%
  • 1 - 2 秒佔比從 15.6% 最高提升至 18.7%
  • 2 - 3 秒佔比從 4% 最低下降至 1.8%
  • 3 - 4 秒佔比從 1.1% 最低下降至 0.6%
  • 4 秒以上的佔比從 2.1% 最低下降至 1.4%

 

參考資料:

長的 JavaScript 任務是否會延遲您的互動時間?

狙殺頁面卡頓 —— Performance 工具指北

chrome performance看瀏覽器渲染過程

深入理解瀏覽器解析渲染 HTML

Vue CLI 計畫頁面開啟時間優化:從16秒到2秒內

preload 讓載入和解析解耦