

項目介紹
整個項目大概有60+個頁面,用到的組件大概150+,package里面的依賴大概有70+個,應該勉強算得上是一個中型的React的項目了。
下面給大家看看我們現在build一次項目的結果--

打包時間約150s,打包完之后的資源gzip之后約1.2m,盡管之前分離了一些公用依賴,但是index包的體積達到了600+還是令人難以接受的。
需要解決的問題 && 思考過的方案
開始優化之前,最重要的就是搞清楚我們到底要優化什么。確定了優化的目標才能著手思考優化方案,進而實施優化方案。結合對項目的bundle分析以及自身對項目的了解,我們初步可以定出以下幾點優化方向--
① 體積瘦身
首先我們需要足夠了解我們的項目,才能著手進行瘦身。在這里有一個很給力的工具可以推薦給大家webpack-bundle-analyzer
盜用一下github上的圖

如圖所示,我們可以很清晰的看到每個js文件里的module組成,還可以看到每個module的大小以及module的組成成分,這對我們分析代碼冗余以及優化方向都能夠提供很大的幫助。
具體食用方式也很簡單--

這樣一來我們就對項目有了一個比較具體的認識,大到項目的依賴一覽,小到某個頁面的組件引用都能在分析報告中找到。接下來就可以開始我們的瘦身之旅了。
打團先找大哥
當我們第一次看到bundle的分析報告時,總能找到一些出乎意料的“大個子”,如果是必不可缺的依賴則沒辦法,但如果是一些可以被取代的依賴就有別的說法了。這里剛好可以看看之前我對create-react-app中moment.js依賴的處理,如果處理順利的話可以很直觀的看到bundle大小的變化。
總結來說,如果有小的并且滿足需求的依賴可以替換,請不要遲疑;但如果沒有可滿足的依賴,可以嘗試自己造一個輪子,當然后者需要結合自身狀況考慮。
分散站位
相信大家在開發網站的時候都用到了不少依賴,但是這些依賴在輸出之后是和業務代碼打包在一起的,這個明顯不符合我們的預期。面對這些基本不會變更的依賴,我們更傾向它們能夠主動抱團并且遠離我們的業務代碼。
這時候,就該使用webpack的CommonsChunkPlugin(貌似在wp4中已經被別的插件替代了,在此我們先不討論wp4),它可以幫助我們將一些指定的module打包進指定的bundle里。具體使用方案可以參考wp官網中的相關介紹,有一個坑點就是--倘若希望負責集合依賴bundle的文件名在打包時不變,則需要生成manifest。
不要將頁面都放到一個籃子里(一)-- 頁面分離
我們可以將一些低頻頁面徹底拉出項目,拿我們的項目來說,一共60+個頁面,用戶大多都是只會訪問其中的幾個或十幾個頁面,不可能將所有的頁面都訪問一遍。這樣一來就必然會有一些頁面的訪問頻率相比之下會十分低下,該怎么處理這些頁面呢?這里有幾種方案:
- 脫離框架重寫相關頁面并重新部署
- Copy項目代碼,在其他地方重新跑一次并部署,原項目就可以刪除不需要的頁面
- 上一方案的簡化版,復刻項目環境,跑一個新的純凈項目并部署,將原項目低頻頁面“剪切”到新的項目中
以上三種方案個人覺得各有優劣,第一個方案很簡單
,適用場景就是類似Q&A的靜態頁,可以將其脫離項目,寫成靜態頁部署在其他的地方,但是不好的地方就是可能沒法復用原項目組件。
方案二呢則是時間至上
的方案,可以做到快速遷移,但是不好的地方在于遷移出去的頁面其實還是塞在一個籃子里,只不過換了個新的籃子罷了。
方案三則是質量至上
的方案,以時間作為成本,換來一個新的“低頻頁面項目”。具體要使用哪種方案,我覺得也是根據當前項目狀況而定,不追求最完美,追求最合適。
② 首屏加載
首屏加載,大概是優化的永恒話題,所有的優化都避不開這一個話題,因為只有它能最直觀的讓“大家”都感受到我們這次優化的成果。對于用戶來說,認為網頁首屏很快的標準其實很單一,就是一打開頁面,看了多久的白屏。所以我們需要做的就是弱化用戶對白屏的感知
,圍繞這一點,個人認為,首屏加載這一優化可以有兩個方向:一個是速度,另一個是體驗。
引入加載占位
其實這個就是前段時間很火的“骨架屏”,我們可以在頁面真正被渲染出來之前,先給用戶看到一個“假的”頁面,等到某個時間節點(例如數據已經準備完畢...)就將真正的內容替換上去。這里有一個我寫的很不走心的例子:)

在這個優惠券列表頁面我的處理方案是,初始化頁面的時候就渲染3個列表項骨架,等待接口數據返回就將真實內容替換上去。
在我們的首屏其實也是類似的,我們可以根據首屏的展示結構,做一個匹配的骨架組件,然后按需求進行展示即可,這樣可以有效減少用戶看到白屏的時間。下面是我這個骨架的代碼,優化的空間很大,不過由于優先級不是很高,所以就沒有進行迭代了。

大概結構就是這樣,樣式方面很粗暴,因為每一項都是獨立的一個組件,直接可以用absolute定位堆砌一個簡潔的占位列表項。里面那個類似進度條的效果則是通過css3的animation實現的,我們可以將每個block的背景色變成漸變的,然后通過background-positon的變化來達到圖中的效果。
圖片懶加載
這應該是個老生常談的優化方向了,原理大概是將視圖之外的圖片都用同一個占位圖進行占位,將其真正的圖鏈接存在data-*中,通過監聽滾動來判斷圖片是否進入視圖中,來控制img標簽src的值。具體的實現很多地方都能搜到,大家可以根據自身情況,按需選擇。
不要將頁面都放到一個籃子里(二)-- 懶加載
其實在整個優化過程中我的重心是放在這個地方的,其他的都是半路上想到的...
讓我們回想一下,上面我們講過將低頻頁面分離,那么,必然就有會那么幾個訪問量十分高的頁面,那么對于這幾個頁面應該怎么辦呢?
因為訪問頻率高,所以我們可以認為這些頁面與我們的核心業務是強相關的,所以將其分離就顯得不那么劃算了(很可能會出現維護多套代碼的窘況)。
但是這樣高頻頁面才是優化的重點區域呀,應該怎么辦呢?面對這樣頁面我們還是可以使用懶加載大法(頁面懶加載 || 組件懶加載 || 依賴懶加載)。
想要在js層面實現各類懶加載,我們都需要借助webpack中的特性Code Splitting,它可以將我們本來打包在一起的js分解成一塊一塊,并能達到按需加載并使用的效果。
- 頁面懶加載
因為我們使用了react-router,所以我們可以使用react-router的getComponent輕松達到頁面懶加載這一需求。如下圖所示,將mainpage這樣引入route的話,在打包的時候會將其分離成一個獨立的js。

- 組件懶加載 && 依賴懶加載
組件和依賴的懶加載也是十分簡單的,如下圖這樣寫就能達到懶加載的效果,但如果我們使用了babel則需要修改一下babel的配置,讓它能夠順利解析動態import()的語法。

③ 打包提速
我們通常的優化都是為了用戶而優化,但其實為了我們自身能夠良好的開發體驗,也應該為開發人員優化優化開發體驗,打包優化則成了不二之選。
使用DLL為打包保駕護航
由于時間原因,在公司的項目中并沒有嘗試使用DLL,但是看到網上有不少同學都推薦介紹了它,所以我選擇在此提及一下~有關于webpack DLL的文檔
將webpack版本從2.0 --> 4.x
由于項目是在差不多一年多以前正式啟動的,所以接手的時候是webpack1.x,在剛接手的時候為了懶加載硬是升級到了2.x。
但是到現在發現,2.x好像也不夠用了,畢竟已經落下了兩個大版本了,更新之后的新特性、新功能或是新優化都應該成為我將項目遷移至新版本的動力。wp4具體的配置細節,在掘金上就見到過挺多同學介紹的,這里我想介紹一下,我是怎么將舊項目遷移到wp4的:
在開始進行版本遷移之前,我設想了兩個方案,一個是在原有項目上直接升級并修改配置;第二個方案是新建webpack4項目,搭建好之后將業務代碼遷移過來。
經過對成功率以及時間成本的評估,我最后選擇的是第二個方案。那么這個新建的項目應該完善到什么程度才能進行遷移呢?我個人是經過以下幾個步驟--
- 搭建項目骨架
這回的項目和之前的都不太一樣了,我們沒有借助大神們的腳手架來搭建項目骨架了,我們需要自己從零開始一點一點的摸索webpack的用法以及新舊版本的差異。
關于一些基礎的知識以及配置十分推薦查閱webpack官網的文檔以及一些之前參考過的文章webpack4-用之初體驗、webpack 4.0.0-beta.0 新特性介紹。
相信大家看完這些之后都會對wp的配置有基本的認知,緊接下來就是建目錄、裝依賴巴拉巴拉。最終我們會得到一個這樣的目錄結構--

- 寫個Hello World!
我們應該如何判斷將項目代碼遷入新項目的時機呢?很簡單,當這個新項目可以正常的調試或打包一個相應框架的Hello World即可。
拿我們的項目來說,搭建完項目目錄以及一些基礎配置之后,接下來就是完全模擬原有項目的技術棧,在新的項目中寫幾個簡單的demo頁。當然這些demo頁并不是隨便寫的,是帶有目的性的,按我這次的經歷來說,我寫了這么幾個文件index.js、App.js、Hello.js、Global.scss、router.js。
剝開非核心依賴,我們最核心的依賴其實就是react & react-router & sass,只要webpack能夠正確的解析es6和sass我們就能很大程度的還原舊項目的環境。(babel中與jsx相關的配置在package里)

- 仔細研讀package
上面的demo完成之后,我們新項目就初具雛形了,接下來我們就需要將舊項目package.json遷移到新項目中,這里需要注意的幾點是:
① "scripts"中的指令要注意,我們要看里面的每條指令分別有什么作用,然后再思考應該怎么在新項目中寫一個功能一樣的指令。
② "dependencies" && "devDependencies"舊項目的依賴也應該無縫遷移過來,不過我們可以趁這個機會把沒有用到的依賴剔除出去。
③ "babel" || "autoprefixer"等輔助工具的配置也應該與舊項目保持一致。
- 遷移項目&修修補補
上述步驟都跑通之后,就能刪掉原有的demo,將舊項目的所有業務代碼都遷移過來。接下來就看著報錯,一個一個修復即可。這里遇到這么幾個坑,困擾了我許久。頁面很順利的遷移過來了,依賴補全之后也順利的跑起來了。
但是在dev環境下切換頁面老是會404。相信大家看到這里就懂了,我用了history模式的路由,在devServer中應該要加上這樣一行配置
devServer {historyApiFallback: true
}
好啦,404消滅之后又有新的狀況了,靜態資源老是引用不到,這是為啥?其實這是因為我們在output的時候沒有設置publicPath引起的,在dev的webpack.config中我的output是這樣配置的
output: {path: path.resolve('dist'),publicPath: '/'
},
// ...
devServer = {contentBase: './dist',port: 9000,historyApiFallback: true
}
這個問題解決之后,我們的開發環境算是還原的差不多了。接下來就該踩踩打包的坑了,我遇到的第一個問題就是,打包完成之后,文件夾里面只有打包輸出,index.html咋不見了...這說好的不太一樣。后面發現是少了copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin')
const config = {// ...plugins: [new CopyWebpackPlugin([{from: 'public', to: ''}])]
}
加上這個依賴之后,我們public文件夾的內容就會乖乖的在dist里面出現。但緊接著又出現新的問題了,第二次打包的時候,怎么dist沒有被清空呢?和上面一樣,年輕的我少用了一個插件clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')let cleanOptions = {root: path.join(__dirname, '..'),verbose: true,dry: false
}const config = {// ...plugins: [new CleanWebpackPlugin(['dist'], cleanOptions),new CopyWebpackPlugin([{from: 'public', to: ''}])]
}
完成上述配置后,每次打包webpack都會清空dist文件夾,并且在打包完成之后,將public中的內容復制到dist。好了看來應該可以了,但是在本地開了個服務器跑頁面的時候發現,各種靜態資源404。這又是什么玩意?實話說在這里踩坑時間是最多的,但是解決方案又是令人窒息的簡單...都怪自己沒有好好看文檔
const CleanWebpackPlugin = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')let cleanOptions = {root: path.join(__dirname, '..'),verbose: true,dry: false
}const config = {output: {filename: 'static/js/[name].[chunkhash:8].js',chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',publicPath: 'http://localhost:5000/' // !!!這里一定要使用絕對路徑,不然就會被坑到}// ...plugins: [new CleanWebpackPlugin(['dist'], cleanOptions),new CopyWebpackPlugin([{from: 'public', to: ''}])]
}
總結
好了,不知道有多少同學會看到這里,先謝謝大家看我在這嘮叨一堆~各類優化的方案在網上看了好多好多,但是好像大家都只講方案沒有涉及實踐,等到自己真正去玩的時候才發現,其實優化沒有想象中那么簡單,要兼顧原有的,又要盡量使用更新更好的,很多時候都會在夾縫中取舍。
其實,能夠優化的還有很多很多,請求方面、業務方面甚至是代碼寫法...都是可以優化的,但是這些怎么能一蹴而就呢?還是得走一步,看一步,選擇最適合自家項目的優化方案才是最佳方案~