同步自我的部落格
開門見山,由於我們專案的前端程式碼只有一個bundle
,所有程式碼都在一個js檔案裡,隨著功能不斷的堆疊,體積已經到無法忍受的地步了(gzip後即將突破300k),導致首屏的時間不停的漲啊漲,最近一週富裕了一點人力趕緊做一次優化,暫時緩住了勢頭。
react+webapck的優化文章現在一搜一大把,這次只說說我的技術方案,以及我是如何分析和優化的。
技術方案
首先,我先說下,gzip
,cdn
等等前端優化手段我們都是有的,這次主要是為了解決bundle
過大的問題。
我們專案的總體架構很簡單易懂,react
全家桶:react
+redux
,哈哈。共有三個系統,每個系統有自己單獨的工程。由於是Hybrid
應用,為了儘可能貼合APP
內的切頁效果,每個工程又都是採用的是單頁+多頁的形式。
由於系統多+單頁多頁混合,導致我們的路由比較混亂,本來打算上react-router
進行一次大的改版,但是時間不夠充裕,綜合時間,開發成本角度來看,我定了一個簡單並可以快速實施的方案:
- 抽取出三個工程的公共程式碼打包出一個
bundle
。比如react
,redux
,redux-thunk
以及一些polyfill
,因為這些都是長時間不會變動的程式碼,所以可以最大程度的命中快取,並且每次釋出不需要重新下載這部分程式碼,在專案中就用externals
的方案去引用這些程式碼。 code split
,每個工程按照多頁的路由做程式碼切割,每個多頁路由都有自己的bundle
。- 非同步載入,每個多頁路由都會對應各自的輔助頁面和元件程式碼,都使用非同步載入的方式。
如何優化bundle體積
code split+非同步載入
這個很簡單,使用webpack
的requre.ensure
就可以了
addPage(cb){
require.ensure([], function () {
cb({
Page1: require('../../pages/Page1'),
Page2: require('../../pages/Page2')
})
});
}複製程式碼
這裡Page1
和Page2
的程式碼都是非同步載入的,具體的部分涉及到路由的設計,我們目前的方案非常的簡單和隨便,就不細說了。我也看了react-router
的解決方案,覺得寫起來複雜了一些,後續可能會在尋找更好的方案或者自己擼一個。
這裡需要注意,雖然我們的js
程式碼做了拆分,css
檔案還是希望打包成一個,所以需要給ExtractTextPlugin
增加allChunks
的配置。
new options.ExtractTextPlugin('[name]@[contenthash].css', {allChunks: true})複製程式碼
externals
我將react
,redux
等等公用的程式碼打包成一個lib.js
,然後暴露在全域性變數上。每個工程裡都會先去引用這個lib.js
,在webpack
的配置裡就只需要配置上externals
就可以了
其他外掛
我們還用了一些其他外掛來儘可能的優化體積
UglifyJsPlugin
不多說了,程式碼混淆壓縮DedupePlugin
消除重複引用的模組,好像webpack2
已內建,使用webpack2
的可以忽略OccurrenceOrderPlugin
讓依賴次數多的模組靠前分到更小的id來達到輸出更多的程式碼CommonsChunkPlugin
這個我們沒有用,但是大家可以去看看,對於多頁應用它可以幫助你將一些公共程式碼打包
如何分析bundle過大的問題
在做程式碼拆分的時候我曾遇到過一個問題,如何確認我的程式碼已經是最優的了,如何確認我無法繼續優化了?這就需要我們去檢視,這個bundle
究竟打包了哪些程式碼,是否這些都是我們需要的。
首先我推薦一個網址,這裡介紹了很多webpack
優化的工具。
我自己推薦兩個bundle
體積的視覺化分析工具
具體如何使用我就不介紹了,它們的文件寫的很清楚大家可以去文件上看,他們都可以很清楚的看到每個bundle
分別打包了哪些程式碼,哪些佔據了最大的體積,也可以觀察哪些程式碼其實是無用的可以優化掉的。
這裡我還遇到過一個問題,比如我在detail.chunk.js
裡發現引入了一個loading
元件,但是我映象裡詳情頁並沒有引入loading
元件呀,這時候就需要去尋找loading
是被誰依賴了。之前我都是用webstorm的find usages
一點點的去看引用關係,其實可以用webpack
提供的一個官方工具來做這件事。
首先,你需要這麼啟動webpack
webpack --profile --json > stats.json複製程式碼
此時會生成一個stats.json
檔案,之後在官方分析工具裡上傳檔案即可對你的bundle進行分析。
這裡我用官方的例子簡單說下
上傳你的
json
檔案,長傳後會看到這麼一個介面,會簡單描述你的webpack
的版本,有多少modules
,多少chunks
等等點選
chunks
,可以看到所有chunks
的描述,左邊是chunks
的id,然後有namse
,有多少modules
,大小,引用它的chunks
是誰、即parents
,假如我們需要分析id
為1的chunk
,只需要點選左邊的id
這裡你可以看到更詳細的資訊,這裡最重要的是兩個,
reasons
是引用這個chunks
的模組,modules
是這個chunks
所引用的modules
這裡你發現有一個模組不是你想要的
modules
,你只需要點選這個模組的id,再去檢視reasons
就可以看到這個模組是被誰引入的
如何優化本地開發體驗和打包速度
webpack吐槽的常態 —— 打包慢,這裡說一下我們這邊做過的優化。
縮小檔案搜尋範圍
將resolve.modules
配置為node_modules
,像使用 impot _ from "lodash"
這種時webpack遍歷向上遞迴查到node_modules
,但通常只有一個node_modules
,為了減少可以直接寫明node_modules
的地址
loader
也可以設定需要生效的目錄地址
比如babel
的loader
可以只對src目錄裡的程式碼進行編譯,忽略龐大的node_modules
{
test:/\.js$/,
loader:'babel-oader',
include:path.resolve(__dirname,'src')
}複製程式碼
使用alias
釋出到npm的庫大多包含兩個目錄,一個是放cmd模組化的lib目錄,一個是所有檔案合併成的dist目錄,多數入口檔案是指向lib的。預設情況下webpack會去讀lib目錄下的入口檔案再去遞迴載入其他以來的檔案,這個過程非常耗時,alias
可以讓webpack直接使用dist目錄的整體檔案減少遞迴
使用noParse
有些庫是自成一體,不需要依賴別的庫的,webpack無需解析他們的依賴,可以配置這些檔案脫離Webpack解析。
happyPack
happyPack的文件也寫的很好,就不復制貼上了,大家可以自行去閱讀文件,簡單地說,它主要是利用多程式+快取使得build
更快,這大幅減少了我們在編譯機上編譯的時間。
後評估
先說說優化完後的結果,由於react
的體積過大,lib
就有60k+,基本已經不能繼續優化了,加上我們的路由設計的很不好,首屏的bundle依然有70k+,總的來說,首屏從280k降低到140k左右。
但是,根據監控的效果來看,頁面js下載的總體時間和白屏時間都只降低了30%左右,這並不符合我的心理預期,想了想除了http請求變多以外並沒有別的副作用,後續會繼續深入的分析一下為什麼優化的效果沒達到預期。