前言:最近重構一個項目(基於umi2腳手架搭建的),打包上線后發現包非常大,決定將項目優化一下,打包后的dist文件
可以看到打包后的dist文件有16M,然后部署上去發現首次打開蝸牛🐌般的速度,原因有一個公共依賴文件有7.6M之大,我giao,這怎么行呢!?
如果瀏覽器選項勾選了不允許緩存,那么將導致每次打開頁面或者刷新都將會幾乎如同首次加載一樣,加載這些文件,每次都這么慢,致命致命致致命。
這在項目部署上線,用戶訪問時候是非常致命的,下面我們開始針對這個進行優化:
一、壓縮靜態資源
先從靜態資源入手:圖片。看下截圖文件ued給的大小居然一張圖片2M多,這...,索性自己來
使用這個網址: https://tinypng.com/ 這個網站可以在保持清晰度變化不大的情況下把圖片大小壓縮,把稍微比較大的圖片上傳壓縮, 可以看到,左側是原圖大小,右側是壓縮后的代碼大小
把文件替換了,使用壓縮后的圖片。
二、使用打包分析工具---去除不需要的依賴
webpack的打包分析工具,我項目用的是umi2腳手架,所以直接根據文檔配置即可,在.env文件新增:ANALYZE=1,然后umi build打包,會自動打開打包后的分析圖。
Stat size :代表原始文件大小(文件沒有經過任何處理的)
parsed size :解析后的文件大小,輸出壓縮過后的文件大小。(右鍵查看文件屬性大小和這個大小一致)
gzipped:經過gizp壓縮過后的代碼大小(例如nginx可以開啟gzip壓縮),這里實際也是最終打開頁面下載的文件大小是這個大。
把圖上的包模塊標注分析一下,紅色的是目前以知的插件依賴,其他是引用的插件中有需要用到的插件:
首先分析下:echart、G6關系圖譜、超圖(地圖)、代碼編輯器這幾個都是我項目中首頁加載不到的(依據你的項目情況而定),我們需要把他從vendors.async.js文件中抽出來。這樣就可以在打開首頁時候(第一次加載),避免引入不需要的插件代碼。
1、先從moment入手,時間插件上有很多國際語言,我們不需要用到,需要將它過濾掉:
在.umirc.js文件加入webpack,umi里面使用的是 chainWebpack
具體寫法如下:
chainWebpack(config) { //過濾掉momnet的那些不使用的國際化文件 config.plugin('replace').use(require('webpack').ContextReplacementPlugin).tap(() => { return [/moment[/\\]locale$/, /zh-cn/]; }); },
接下來,umi build打包測試看效果moment時間依賴包體積明顯了500k左右:
2、處理下echart,寫法換成按需引入,打包時候也會按需打包
再次打包查看,dist文件大小,明顯小了非常多(可能圖片主要靜態文件大小)!
我們發現即使這么做了,雖然把項目總體減小了,但是加載時候還有需要加載一個超級大的主文件,這就是一個問題, 之后我們應該用包分析模塊顯示的大小來做參考調整分割。進行第三步現在~~~
三、使用webpack分割代碼---把主文件的依賴分離出來
以上步驟完成以后開始優化項目加載的具體操作,這里解釋下為什么要這么做,因為我們通過分析發現沒分割之前,瀏覽器打開頁面時候下載的那個vendor.async.js文件非常大,並且里面的依賴不是我們每次都要用到,所以我們需要把這個文件拆分。
整理思路:拆分成什么樣?我們需要把不是每次用到的依賴拆分出來,讓他使用到的時候再根據那個頁面按需要加載。
需要根據實際情況來定奪,實際目的只有一個,把主文件(里面的公共依賴文件用得比較少的)分割出來,多出引用的放在主文件里面(避免每次都要加載那部分的依賴代碼文件),例如我項目中:
關系圖譜只有一個頁面有用到、echart只有一個頁面用到、地圖只有兩個頁面用到(一個前台一個后台),考慮到主文件太大因素,需要將這幾個依賴拆分出來,等打開那幾個特定頁面時候按需要去加載這依賴代碼。
開始拆分:使用webpck的 splitChunks
以下是webpack分割代碼字段的解釋說明:
optimization: { minimize: true, splitChunks: { chunks: 'async', //all: 不管文件是動態還是非動態載入,統一將文件分離。當頁面首次載入會引入所有的包 //async: 將異步加載的文件分離,首次一般不引入,到需要異步引入的組件才會引入。 //initial:將異步和非異步的文件分離,如果一個文件被異步引入也被非異步引入,那它會被打包兩次(注意和all區別),用於分離頁面首次需要加載的包。 minSize: 30000,// 文件最小打包體積,單位byte,默認30000 minChunks: 1, //最少引入的次數 2:引入兩次及以上被打包 automaticNameDelimiter: '.',// 打包分割符 cacheGroups: { vendors: { // 項目基本框架等 chunks: 'all', test: /(react|react-dom|react-dom-router|babel-polyfill|react-redux)/, priority: 100, name: 'vendors', }, echartsVenodr: { // 異步加載echarts包 test: /echarts/, priority: 100, // 高於async-commons優先級 name: 'echartsVenodr', chunks: 'all', minChunks: 5, //最少引入的次數 2:引入兩次及以上被打包 }, }, }, },
chunks:有三個值:分別代表你想優化打包優化的類型,默認為async,截圖看下這個意思
async:動態引入

initial:直接引入加載
all:以上兩者都進行打包分割優化
最終代碼:
chainWebpack(config) { //過濾掉momnet的那些不使用的國際化文件 config.plugin('replace').use(require('webpack').ContextReplacementPlugin).tap(() => { return [/moment[/\\]locale$/, /zh-cn/]; }); config.merge({ optimization: { minimize: true, splitChunks: { chunks: 'all', //all: 不管文件是動態還是非動態載入,統一將文件分離。當頁面首次載入會引入所有的包 //async: 將異步加載的文件分離,首次一般不引入,到需要異步引入的組件才會引入。 //initial:將異步和非異步的文件分離,如果一個文件被異步引入也被非異步引入,那它會被打包兩次(注意和all區別),用於分離頁面首次需要加載的包。 minSize: 30000,// 文件最小打包體積,單位byte,默認30000 minChunks: 1, //最少引入的次數 2:引入兩次及以上被打包 automaticNameDelimiter: '.',// 打包分割符 cacheGroups: { //優先級高於外面配置,理解為先分割后合並 vendors: { // 項目基本框架等 chunks: 'all', test: /(react|react-dom|react-dom-router|babel-polyfill|react-redux)/, priority: 100, name: 'vendors', }, echartsVenodr: { // 異步加載echarts包 test: /echarts/, priority: 100, // 高於async-commons優先級 name: 'echartsVenodr', chunks: 'async', // minChunks: 5, //最少引入的次數 2:引入兩次及以上被打包 }, antvVenodr: { // 異步加載@antv包 test: /@antv/, priority: 100, // 高於async-commons優先級 name: 'antvVenodr', chunks: 'async', }, 'async-commons': { // 異步加載公共包、組件等 chunks: 'async', minChunks: 2, name: 'async-commons', priority: 90, }, commons: { // 其他同步加載公共包 chunks: 'all', minChunks: 2, name: 'commons', priority: 80, }, }, }, }, }); },
實際效果:可以看到echart包、g6、codemirror這幾個插件被單獨抽離出來成js文件,不會再沒用到情況下加載引入。
補充:在較早之前的版本,我們知道webpack可以將js文件打包壓縮成js.gz文件,使得文件體積大大減小,但是現在大部分前后端分離的場景都是將前端項目部署在nginx,nginx上有個功能能將js壓縮成gizp格式傳輸給瀏覽器,這也是為什么現在大都打包成js格式就可以,因為nginx上只要開啟壓縮功能,他會檢測是否有.gz文件,沒有的話會自動將js文件壓縮傳輸。
總結:
1、首先通過webpack手段優化項目,(開啟按需加載前提),只能對打包的代碼分割,總體積上基本不可有很大的改變(某些情況可能會導致dist總體積變大,因為分離公共依賴包,能把公共的東西從vendors.async.js文件提取出來,同時也會把這個依賴拿出來在放到另一個或者另兩個js文件里面做到按需加載,好處是能使得某個頁面時候不需要一次引入這么大的一個文件。)
2、通過修改代碼優化:例如將插件通過Index.html內的script標簽方式引入(外網環境可以直接用CDN方式),這樣可以使得公共依賴包減少體積。
3、簡單粗暴圖片壓縮(webpack也有其他的圖片轉換壓縮,例如將小於多少kb的圖片轉換為base64格式)
4、性能優化:多寫PureComponent組件而不是普通的Component組件,因為pure有自動判斷shouldComponentUpdate這個組件生命周期的功能(也只能進行淺比較:數組、對象類型,因為存儲堆棧,引用地址指向問題還是會頻繁更新)。備注:如果有寫hook組件,那么使用hook的memo,usememo,usecallback,是最佳方案!