供應鏈管理后臺秒開體驗優化
1、背景
供應鏈管理后臺以下簡稱 SCM
隨著業務不斷迭代,SCM 的頁面也變得越來越多,置身于持續增加、優化系統功能層面的需求,漸漸忽視了使用者對系統體驗層面的訴求。
近期通過線上反饋渠道收集到的問題中,有不少都是關于頁面打開速度比較慢,為了能夠提升系統使用體驗和效率,我們對 SCM 的打開速度做了些針對性的迭代優化。
2、現狀
目前 SCM 使用 Vue 2 全家桶,基于 vue-cli-service 開發、構建,菜單數量繁多,通過業務域拆分為若干個子應用(React 技術棧)的遷移工作也在有序進行中。
通過效率數據看板可以查看 SCM 的秒開率統計數據(關于秒開指標 FMP 的計算方式可以參考首屏統計的前世今生)。通過下圖可以看見,優化前的秒開率基本都在 20% 以下,而且數據會跟著發版頻次有所波動。
3、思路
提起前端性能優化,大家腦中或多或少的都會冒出一些想法,隨手一搜,也能看到各種最佳實踐之類的萬字長文。為了避免出現工作做了很多,卻沒對性能提升有顯著效果的情況,在優化工作開始之前,首先是要對系統做診斷,并確定優化要達到的關鍵結果及衡量指標。這里我們只需要用到兩個工具來輔助查優化工作,通過不斷優化,不斷驗證以達到想要的效果。
使用 Chrome DevTools 的 Performance 選項卡找出頁面性能瓶頸。如下圖所示,通過 Network 區域顯示的靜態資源/接口請求的瀑布流,及 Main 區域主線程運行過程中每個 Task 的執行明細,能夠很方便的找出影響頁面性能的因素。關于使用如果使用 Performance 可參考官方教程 Analyze runtime performance。
使用 webpack-bundle-analyzer 查看編譯打包后的文件,驗證打包策略是否合理、是否存在冗余模塊等。使用 vue-cli-service 的項目可在打包命令后添加 --report 開啟;umi 項目中可在打包命令前添加 ANALYZE=1 開啟;其它 webpack 項目可安裝 webpack-bundle-analyzer 依賴包按需使用。
4、優化之路
首先要解決的如何在最短時間內獲取到頁面所需的最小資源。
4.1 靜態資源
- 控制 html 文件大小
由于 TCP slow start 算法的限制,應盡可能把 html 文件大小控制在 14kB 以內,使得 html 內容能在一個 TCP packet 中發送到瀏覽器(可參考why-your-website-should-be-under-14kb-in-size)
優化前的打包策略是把 runtime chunk 提取到了 html 文件中,估計是之前為了減少請求個數,但因為接了 Assets后,已全站靜態資源 CDN 化后可基本忽略該方面的影響。故剔除 html 文件中的 runtime chunk 內容后,文件體積由 18kB 降至 1kB 以內。
- 公共包體積優化
通過打包分析工具顯示,@du/earth 這個包占用了公共包近 40% 的體積大小。
熟悉 Vue 的同學都知道,常見的 Vue 的組件注冊方式有兩種:
- Vue.component(id, [definition]),全局注冊
- {components: { 'component-a': ComponentA }},局部注冊
第一種常用于項目依賴的基礎組件庫,如element-ui,由于組件庫本身會提供 install 方法,可以使用 Vue.use(ElementUI) 很方便的把全部組件注冊到全局。如果沒做組件按需打包的話,這種引入方式會把 element-ui 整包打入。
但在代碼分析的過程中,發現@du/earth(可理解為基于 element-ui 的高階組件)也是采用這種全局注冊的方式,在一番查找比對后(大大的體力活兒),項目代碼里卻只用到了其中 4 個組件。
優化效果如下圖,chunk-libs 的提交直接由 1.4MB 降到 730kB,降幅 50%。其實這種基本沒啥工作量,但收益卻是巨大的。
另外通過頁面代碼分析,其實有些組件不是首次進入需要用到的,我們可以采用動態引入的方式。例如:
還有些依賴包的打包策略不合理,還需要推進依賴包的優化。例如 @du/umi-request 2.x 版本包,會打包進一些 node.js 的模塊,因為歷史原因,我們暫無法升級到 3.x 版本,所以就找到包的維護者,在 2.x 版本的基礎上改變依賴包的引入方式,更新下 2.x 版本。
- Assets 打包優化
借助于發布平臺 assets 發布,前端應用可以很好的利用 CDN 特性,帶來更快的資源請求速度外,還能支持秒級回滾。但目前的接入方式是需要前端應用自行加入版本號概念,如果版本號更新,每次編譯打包后都會把構建產物放到新的版本號目錄下,會導致基于 contentHash 的打包策略,無法充分利用瀏覽器緩存。為了解決這個問題目前有兩種解決方式(如果你有更好的方案,歡迎留言分享):
設置 externals。把一些不易變動的基礎框架包,通過 unpkg 或自行上傳至 CDN,以 script src 的方式寫入 index.html 文件中。并在打包配置文件中將這些依賴包設置到 externals 配置項中:
使用 DllPlugin 提前打包。對于有些不易變動,但可能需要本地打包的依賴包可使用 webpack DllPlugin 提前打包。在 vue-cli-service 項目中可通過添加 vue-cli-plugin-dll 依賴,并在 vue.config.js 中配置:
在我們拿到 dll 的產物后,目前是采用上傳到 CDN 的方式,引入到 index.html 文件中,后面可以通過 CopyWebpackPlugin 和 HtmlWebpackPlugin 加入打包流程中。
- 小結
大概通過以上的一些優化手段,可以把 chunk-libs 這個公共依賴包的提交降到 200kB 以內。
4.2 頁面渲染
SCM 使用的是后臺應用典型的頂部-側邊布局-通欄的界面布局。
- 提前渲染主體布局
目前 SCM 的路由是通過獲取用戶天網權限菜單,和本地已聲明的路由執行 merge 操作,并在 Vue 實例化之前添加到 vue-router 的路由表中。當前這種方式就需要在開始主體布局渲染之前要等待菜單接口請求完成。從上報的接口請求時長的數據中顯示,菜單的接口請求耗時大約在 200ms 左右(全菜單權限的情況下),如果能夠把菜單的數據中緩存下來,對于秒開來講,這里節省的耗時還是挺明顯的。因此,在原菜單接口請求的邏輯里加上優先取緩存的邏輯。偽代碼如下:
另外,優化前嵌套的路由聲明方式是把 Layout(菜單欄+頂部欄)作為嵌套視圖的一部分,也就是只有等到當前路由被 resolve 后才能渲染主體布局。之所以使用這種方式,是考慮到存在路由頁面不需要 Layout 的場景,但在分析過后,這種場景其實相當很少,可兼容處理。偽代碼如下:
- 耗時任務優化
通過 DevTools 的 Performance 可以找出 Long Task,或比較耗時的 js 代碼執行片段。這里可以使用本地代碼運行,能比較直觀看出方法名及實際代碼所在文件等。但因為受當前電腦運行狀態影響,有時候數據可能會有些起伏,但大體是能找出性能瓶頸的,需要點耐心。
通過分析找出以下比較明顯的點:
首次進入渲染的組件數量偏多??伤阉鞑檎?Vue 的 init 方法。解決:對于不必要的組件延遲渲染,待用戶交互時再渲染。
- 菜單數據較大,reactive 的過程有明顯的耗時產生。
解決:菜單數據僅為展示或篩選使用,可以經過 Object.freeze 后再添加到 store 中。
- 小結
頁面渲染優化的過程中,是相對比較耗精力的,需要不斷通過分析工具的反饋來驗證優化的效果。除了通過 Performance 這種每次都需要 recording 后才能分析,其實還可以借助 performance.now() 打點的方式,來快速驗證優化效果。
5、效果展示
當前階段大體通過以上方式,達到的優化效果如下圖。雖然絕對值仍不算高,但提升幅度還是很明顯的,后面還是會持續根據分析出的問題做迭代優化。
6、寫在最后
前端的性能優化涉及面其實挺廣的,優化手段也有很多,但不一定都適用自己的項目,而且還要考慮 ROI。二八法則同樣適用于做優化這件事,我們可以用 20% 的投入換取 80% 的優化效果,這時候 ROI 是比較高的,因為應用本身可能會有它的復雜度存在。當然優化無止境,后續我們還是要持續跟進用戶體驗層面的訴求,幫用戶解決了功能性的需求的同時,再能提高些效率本身就是件很有價值的事情。