媒介
之前接辦公司一個前端項目,開闢了幾個月後愈來愈難以忍受項目構造的雜沓和打包體積的痴肥(腳手架和基礎功能代碼都是從公司的其他項目複製過來的),假如不馬上舉行重構,不可思議今後要怎樣保護各個產品線。因而我挺身而出負擔了項目框架的優化使命,這裏分享一下我在打包體積優化中所研討的效果,經由幾輪的勤奮,勝利的將我們這個 react
+antd
+immutable
+rxjs
的較大項目從打包后的9MB
下降到了2.5MB
,首屏加載(gzip)從600KB+
下降到了200KB
,而且基礎大將穩固的第三方庫,webpack runtime
代碼和營業代碼完整星散,最低限制削減網站更新時用戶須要加載的代碼量。
空話不多說,下面細緻申明我所做的每一個步驟。
1. 優化第三方庫
項目里對庫的運用較為雜沓,有些庫裝置了但很罕用或許基礎沒用,然則又在webpack
中的vendor
進口指定打包了進來,形成體積上的糟蹋,所以須要細緻評價每一個庫是不是必要裝置。react v16
對照react v15
,加上react-dom
,體積上下降了30%,因而堅決晉級。
2. moment.js
剖析完stats.json
后,發明的第一個題目就是moment
很大,詳細緣由webpack
是把一切的locale
文件打包了進來。我們的項目不須要多言語,因而我們能夠運用ContextReplacementPlugin
插件來捨棄中文以外的其他言語文件:
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/)
去撤除locale
后,又發明了另一個題目:依靠剖析顯現,我的項目里打包了兩份moment
,一份es module
版的,一份umd
版的。經由一番排查后發明,運用import 'moment'
導入的會加載es module
版的,這是webpack
設置的mainFields
決議的,然則在locale
中的言語文件中,它會用相對途徑導入umd
版的moment
,這就致使我的項目里湧現了兩份moment
。為了一致版本,我們將moment
設置為一個別號並指向umd
版:
alias: {
'moment$': path.resolve('node_modules/moment/moment'),
},
// $示意相對婚配
別的另有一個庫dayjs
值得一提,其API
基礎與moment
一致,然則體積僅為幾KB,不知道antd
會不會到場對dayjs
的支撐。
3. ECharts
項目之前是直接運用的完整版的echarts
,而且沒有將echarts
組件抽取為大眾chunk
,效果致使每一個異步加載的頁面組件,只需用了echarts
就會變得碩大無比。
處置懲罰方案:在echarts
官網定製一份僅包括項目所需圖表範例的閹割版,而且將echarts
組件抽取為異步加載的chunk
,如許就只須要加載一次。
關於如何將組件抽取為零丁的chunk
,能夠用import()
語法,或許運用react-loadable
這個庫,它能夠直接將react
組件包裝成異步組件,並在須要時才舉行加載。
4. 抽取異步加載的chunk
中的大眾代碼
上面的步驟抽取echarts
就是指的抽取異步chunk
中的大眾代碼,除了echarts
以外另有許多大體積的大眾代碼,比方種種antd
的組件以及其依靠的底層組件rc-components
,這部份也是我們要提取出來的。我們沒必要將每一個antd
組件包裝為異步組件,這裏只須要設置一下CommonsChunkPlugin
就能夠了:
new webpack.optimize.CommonsChunkPlugin({
async: 'async-vendor',
deepChildren: true,
minChunks: (module) => {
return /node_modules/.test(module.context);
},
}),
在沒有將children
設為true
時,CommonsChunkPlugin
會從進口文件(entry
)提取大眾代碼,這時候就不會對異步加載的chunk
起作用。因而為了提取異步chunk
的大眾代碼,我們設置deepChildren
為true
(children
指的是進口文件的直接子節點,deepChildren
指的是悉數子節點)。async
示意天生一個懶加載的chunk
,只有當須要時才會被加載。
上面只是將第三方庫的大眾代碼提取了出來,假如願望把異步chunk
當中本身的營業代碼提取出來,則能夠修正minChunks
劃定規矩,或許再增添一個設置:
new webpack.optimize.CommonsChunkPlugin({
async: 'async-biz',
deepChildren: true,
minChunks: 2,
}),
5. 並不是每一個路由頁面組件都須要異步加載
項目之前的做法是,每一個路由對應的頁面根組件都須要異步加載,如許做的效果是打包出了許多個chunk
,而有一半的chunk
在gzip
之前體積都不足5KB
,糟蹋請求是一方面,更嚴峻的是影響了首屏加載體積。
這是為何?明顯把每一個頁面都異步加載了,怎樣會影響首屏體積呢?實在緣由就是第三步中的async-vendor
被首屏加載了,該chunk
重要包括了antd
組件,gzip
以後約為120KB
。
關於用戶來講,第一次翻開我們的網站一定是到登錄界面,此時須要完整加載我們的首屏代碼,以後有了緩存,除了營業代碼更新須要加載很小的chunk
以外,理論上是不須要再下載任何代碼的,因而我們須要針對登錄界面舉行首屏優化。
登錄界面包括了登錄、修正暗碼、請求賬號等子路由,之前將這些都打包為異步chunk
,由於這些界面須要async-vendor
當中的某幾個antd
組件,因而首屏加載一定會包括async-vendor
。拆分async-vendor
是一種要領,然則還要剖析究竟用了哪些組件,修改營業代碼后又要從新剖析,顯得很貧苦,最簡樸的做法就是作廢登錄相干路由的異步加載,將其打包到main
當中,同時只需加載須要的antd
組件,因而完整避免了加載async-vendor
,首屏體積得到了大大下降。
6. 星散出webpack runtime
代碼
webpack
在客戶端運轉時會起首加載webpack
相干的代碼,比方require
函數等,這部份代碼會跟着每次修正營業代碼后發生變化,緣由是這裏面會包括chunk id
等輕易變化的信息。假如不抽取出來將會被打包在vendor
當中,致使vendor
每次都要被用戶從新加載,vendor
也失去了它的意義。星散的設置很簡樸:
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),
minChunks: Infinity
示意建立一個什麼都沒有的chunk
,由於不會有任何模塊被無限次引用過,如許webpack runtime
代碼就會被CommonsChunkPlugin
放入這個末了的chunk
當中。
7. webpack
內部優化
這部份內容很簡樸,就兩個插件的運用,HashedModuleIdsPlugin
和ModuleConcatenationPlugin
。
默許情況下,webpack
會為每一個模塊用数字做為ID
,如許會致使同一個模塊在增加刪除其他模塊后,ID
會發生變化,不利於緩存。為了處置懲罰這個題目,有兩種挑選:NamedModulesPlugin
和HashedModuleIdsPlugin
,前者會用模塊的文件途徑作為模塊名,後者會對途徑舉行md5
處置懲罰,下降了文件體積,相比較而言,應當開闢時挑選前者,臨盆環境挑選後者。ModuleConcatenationPlugin
重如果作用域提拔,將一切模塊放在同一個作用域當中,一方面能進步運轉速率,另一方面也能下降文件體積。條件是你的代碼是用es
模塊寫的。
8. babel-polyfill
polyfill
也是體積很大的一部份,然則又不得不加載,關於這部份的優化能夠參考這篇文章,ES6和Babel你不知道的事兒。另有一種要領是運用polyfill.io
,這個處置懲罰思緒個人以為很不錯,然則還不敢在臨盆環境用,先張望張望。
總結
以上內容是我這些天找材料研討的效果,總的來講打包體積算是得到了有用掌握,關於chunk
的打包設置以下:
entry: {
main: path.join(process.cwd(), 'src/index.js'),
vendor: [
'babel-polyfill', 'immutable', 'moment', 'react', 'react-dom' ...
],
},
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].chunk.js',
},
plugins: [
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.CommonsChunkPlugin({
async: 'async-vendor',
deepChildren: true,
minChunks: (module) => {
return /node_modules/.test(module.context);
},
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),
]
webpack 4
已出了,再也沒有CommonsChunkPlugin
了,取而代之的是SplitChunksPlugin
,看來又要研討新的東西了。。。