react項目優化之webpack
開門見山,由于我們項目的前端只有一個 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請求變多以外并沒有別的副作用,后續會繼續深入的分析一下為什么優化的效果沒達到預期。
來自:http://zhangfe.github.io/2017/03/12/webpack優化三兩事/