react 專案優化之 webpack

zhe.zhang發表於2017-03-12

同步自我的部落格

開門見山,由於我們專案的前端程式碼只有一個bundle,所有程式碼都在一個js檔案裡,隨著功能不斷的堆疊,體積已經到無法忍受的地步了(gzip後即將突破300k),導致首屏的時間不停的漲啊漲,最近一週富裕了一點人力趕緊做一次優化,暫時緩住了勢頭。

react+webapck的優化文章現在一搜一大把,這次只說說我的技術方案,以及我是如何分析和優化的。

技術方案

首先,我先說下,gzipcdn等等前端優化手段我們都是有的,這次主要是為了解決bundle過大的問題。

我們專案的總體架構很簡單易懂,react全家桶:react+redux,哈哈。共有三個系統,每個系統有自己單獨的工程。由於是Hybrid應用,為了儘可能貼合APP內的切頁效果,每個工程又都是採用的是單頁+多頁的形式。

由於系統多+單頁多頁混合,導致我們的路由比較混亂,本來打算上react-router進行一次大的改版,但是時間不夠充裕,綜合時間,開發成本角度來看,我定了一個簡單並可以快速實施的方案:

  1. 抽取出三個工程的公共程式碼打包出一個bundle。比如reactreduxredux-thunk以及一些polyfill,因為這些都是長時間不會變動的程式碼,所以可以最大程度的命中快取,並且每次釋出不需要重新下載這部分程式碼,在專案中就用externals的方案去引用這些程式碼。
  2. code split,每個工程按照多頁的路由做程式碼切割,每個多頁路由都有自己的bundle
  3. 非同步載入,每個多頁路由都會對應各自的輔助頁面和元件程式碼,都使用非同步載入的方式。

如何優化bundle體積

code split+非同步載入

這個很簡單,使用webpackrequre.ensure就可以了

addPage(cb){
    require.ensure([], function () {
            cb({
                Page1: require('../../pages/Page1'),
                Page2: require('../../pages/Page2')            
                })
       });
}複製程式碼

這裡Page1Page2的程式碼都是非同步載入的,具體的部分涉及到路由的設計,我們目前的方案非常的簡單和隨便,就不細說了。我也看了react-router的解決方案,覺得寫起來複雜了一些,後續可能會在尋找更好的方案或者自己擼一個。

這裡需要注意,雖然我們的js程式碼做了拆分,css檔案還是希望打包成一個,所以需要給ExtractTextPlugin增加allChunks的配置。

new options.ExtractTextPlugin('[name]@[contenthash].css', {allChunks: true})複製程式碼

externals

我將reactredux等等公用的程式碼打包成一個lib.js,然後暴露在全域性變數上。每個工程裡都會先去引用這個lib.js,在webpack的配置裡就只需要配置上externals就可以了

其他外掛

我們還用了一些其他外掛來儘可能的優化體積

  • UglifyJsPlugin 不多說了,程式碼混淆壓縮
  • DedupePlugin 消除重複引用的模組,好像webpack2已內建,使用webpack2的可以忽略
  • OccurrenceOrderPlugin 讓依賴次數多的模組靠前分到更小的id來達到輸出更多的程式碼
  • CommonsChunkPlugin這個我們沒有用,但是大家可以去看看,對於多頁應用它可以幫助你將一些公共程式碼打包

如何分析bundle過大的問題

在做程式碼拆分的時候我曾遇到過一個問題,如何確認我的程式碼已經是最優的了,如何確認我無法繼續優化了?這就需要我們去檢視,這個bundle究竟打包了哪些程式碼,是否這些都是我們需要的。

首先我推薦一個網址,這裡介紹了很多webpack優化的工具。

我自己推薦兩個bundle體積的視覺化分析工具

  1. webpack-visualizer
  2. webpack-bundle-analyzer

具體如何使用我就不介紹了,它們的文件寫的很清楚大家可以去文件上看,他們都可以很清楚的看到每個bundle分別打包了哪些程式碼,哪些佔據了最大的體積,也可以觀察哪些程式碼其實是無用的可以優化掉的。

這裡我還遇到過一個問題,比如我在detail.chunk.js裡發現引入了一個loading元件,但是我映象裡詳情頁並沒有引入loading元件呀,這時候就需要去尋找loading是被誰依賴了。之前我都是用webstorm的find usages一點點的去看引用關係,其實可以用webpack提供的一個官方工具來做這件事。

首先,你需要這麼啟動webpack

webpack --profile --json > stats.json複製程式碼

此時會生成一個stats.json檔案,之後在官方分析工具裡上傳檔案即可對你的bundle進行分析。

這裡我用官方的例子簡單說下

  1. 上傳你的json檔案,長傳後會看到這麼一個介面,會簡單描述你的webpack的版本,有多少modules,多少chunks等等

    react 專案優化之 webpack
    1

  2. 點選chunks,可以看到所有chunks的描述,左邊是chunks的id,然後有namse,有多少modules,大小,引用它的chunks是誰、即parents,假如我們需要分析id為1的chunk,只需要點選左邊的id

    react 專案優化之 webpack
    2

  3. 這裡你可以看到更詳細的資訊,這裡最重要的是兩個,reasons是引用這個chunks的模組,modules是這個chunks所引用的modules

    react 專案優化之 webpack
    3

  4. 這裡你發現有一個模組不是你想要的modules,你只需要點選這個模組的id,再去檢視reasons就可以看到這個模組是被誰引入的

如何優化本地開發體驗和打包速度

webpack吐槽的常態 —— 打包慢,這裡說一下我們這邊做過的優化。

縮小檔案搜尋範圍

resolve.modules配置為node_modules,像使用 impot _ from "lodash"這種時webpack遍歷向上遞迴查到node_modules,但通常只有一個node_modules,為了減少可以直接寫明node_modules的地址

loader也可以設定需要生效的目錄地址
比如babelloader可以只對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請求變多以外並沒有別的副作用,後續會繼續深入的分析一下為什麼優化的效果沒達到預期。

相關文章