針對第三方套件使用的合宜性來進行審視,並透過 async component 與 webpack 代碼分割功能來實現 Lazy Loading Routes 機制,讓打包後產出的 bundle 檔案能依照各頁面需求載入所需的代碼。
前言
在網站開發過程中,通常被關注的點只會在功能正確性上,但隨著日子逼近上線期,漸漸地就會開始關注透過 webpack 打包出來的那包檔案,並且在意「進入網站」所需載入的檔案大小及時間,此時檔案大小就會成為錙銖必較的議題。筆者整理了一些近期專案的瘦身心得,以下說明。
vue-cli: v2.9.2
vue: v2.5.2
vue-router: 3.0.1
webpack: 3.6.0
檢查 bundle 檔案內含物
在預設情況下,絕大部分的程式碼透過 webpack 打包後的會存在以下兩隻 bundle 檔中:
vendor.[hash].js
: 第三方套件app.[hash].js
: 大部分開發的代碼都於此
要解決檔案過大的問題,首先必須知道這些 bundle 檔究竟包含了那些代碼模組,這樣我們才可以對此進行分析及拆解處理;若是使用 vue-cli 建立的專案,可直接加入 --report
參數來產出這個分析檔案。
在打包的過程中主要是利用 webpack-bundle-analyzer
這個插件來分析各個 bundle 檔案;因此若不是透過 vue-cli 產出專案架構的朋友,也可以直接加入此套件進行分析即可。
建置後就可以看到所有 bundle 檔案各自的內含物,區塊越大表示檔案越大,因此可由此審視過大的組件是否有其存在的必要;另外,也可以做為瘦身計畫的成果表,由此顯示圖形化的成果是否有如預期變化 。
清查使用套件
由於第三方套件在打包後都會放置在 vendor.[hash].js
中,所以稍有套件濫用的情況就會造成該檔案異常腫大,因此這隻 bundle 檔會是我們關注的重點項目之一;透過 bundle analyzer 產出的區塊圖可以很清楚看出檔案相對大的套件項目(因為字特大特清楚),這時可以考慮的方向如下,後續會針對各情境進行說明。
- 是否真有使用到這個套件
- 套件是否包含無用的檔案可移除 (ex. 各國語系檔)
- 套件是否只在少數頁面使用,可作動態載入 (有需要在用)
- 套件是否能夠僅載入需要的部分就好,不要整個都載入
似乎沒使用到這個套件
在開發期間,會因為特殊需求會載入相關的套件來加速開發,但大家都知道需求的變動是很快的,有時候會有需求已被移除但忘記移除套件的情況發生,就會造成無用套件仍然被包進程式中,因此藉這個機會清查一下套件清單也不錯。
套件存在無用的語系檔案 - moment.js
從分析圖可以清楚發現在 vendor.[hash].js
中 moment 套件中被放入各國的語系文字,光是 moment 套件就佔了 221.08 KB 大小,而事實上我們只需要加入系統需支援的語系即可,因此若可以移除其他不須的語系檔應可大幅減少 bundle 檔案的大小。
我們可在 webpack 使用 ContextReplacementPlugin
來設定 moment 只需要 en 及 zh-tw 語系即可。
調整後的檔案已經從 221.08 KB 減為 50.52 KB ,足足瘦身約 171 KB 左右,是不是相當有感!
只有特定頁面才需要的套件 - highcharts.js
先前有提到第三方套件都會放置在 vendor.[hash].js
中,也就是表示進入網站就會載入這隻 bundle 檔案,但若特定套件如 highcharts 只在少數頁面才會用到,而其他頁面也需花時間載入這個沒用到的套件,這樣不是很不符經濟效益嗎?
這時我們應將 highchars 從 bundle 檔移出,透過 webpack 設定將這套件切出 highcharts.[hash].js
獨立的 chunk 檔案,讓有需要的頁面才去下載,這樣才是比較合理的作法。
這樣又讓 vendor.[hash].js
瘦身 221.48 KB,讓進入網站的客戶少花一點時間載入不必要的檔案。
只單獨載入所需模組 - lodash.js
在使用一些 Functional Library 的時候可以特別注意一下 import 方式,是否可以只 import 所需 Function 就好,避免將整個 Library 都載入而造成 vendor.[hash].js
腫大。以 lodash 為例,大家可以考慮 one-by-one 的方式來只 import 所需 function 會比較好喔!
// X: Import the whole library
import _ from 'lodash'
// O: Import specific methods one by one
import map from 'lodash/map'
路由頁面組件分組
當網站開發越多功能時,可以發現 app.[hash].js
逐漸加大,因為預設會將網站中非套件類的代碼都打包至 app.[hash].js
這個 bundle 檔案,這也就表示只要訪問站台,就必須要把整站所有代碼都載下來,這樣會花費許多不必要的等待時間;因此我們可以透過 vue 的 async component 搭配 webpack 設定來拆出各頁面所需的獨立 bundle 檔案,只有在訪問到該頁面時才需載入,實現 Lazy Loading Routes 機制。
以下是一個開發中的專案,在打包後產出 app.[hash].js
約為 2MB,這代表用戶在初次訪問時就必須花費時間等待下載 2MB 檔案只為了顯示一個畫面;而且這只是開發中的專案,如果全部功能都完成後,也許會來到 4MB 的大小,所以必須拆解 app.[hash].js
中的代碼,讓訪問頁面時才載入該頁面所需的代碼。
在 router 中可用 dynamic import 載入各頁面組件,並可依據功能名稱來 group 相關頁面組件在相同 chunk 中,如果打包出來的 chunk 檔仍然過大,也可再考慮細拆讓特定頁面組件自己存在一個 chunk 也行。
當 chunk 切得過細,就又要考慮到是否有重複程式碼不斷出現在各 chunk 中,一直載一樣的東西浪費網路流量;此時可考慮設定 webpack.optimize.CommonsChunkPlugin
的 minChuncks
來定義有多少重複份數代碼散在各 chunk 時需要再抽出成為一個共用 vendor-async.[hash].js
檔案。不過這是雙面刃,如果將該值設太大,表示只有少數組件共用次數超過,所以還是會有重複下載的流量浪費;如果設太小,等於共用兩次的東西就包到共用區,有可能剛進網站根本沒用到該功能就要載入了。
接著調整 Webpack 設定,讓 bundle 檔案名稱具有意義。
完成上述調整後 app.[hash].js
已從 2MB 降到 592 KB,減少約 1.4 MB 無謂的資料下載時間;另外當訪問到特定功能如 mobile 相關功能頁面時,也只需要再加載 mobile.[hash].js
就可以,這樣可以避免不必要的資源一次全部載入。
參考資訊
Optimize your libraries with webpack
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !