本文講解的是最近在做的一個多頁面專案,結合webpack4的splitChunks進行程式碼包分拆的過程
專案框架
專案有home和topic兩個入口檔案,主要包括:
- react、mobx、antd作為專案的基本框架,
- echarts(畫圖)和d3(畫圖)是專案中部分頁面用到比較大的元件庫
- src專案公共元件程式碼等
- 其他的非公共程式碼。
兩個入口檔案都用react-loadable做了非同步載入
import Loadable from 'react-loadable';
...
const LoadableLogin = Loadable({
loader: () => import('../../common/components/login'),
loading: Loading,
});
...
複製程式碼
webpack部分配置相關如下:
module.exports = {
...
mode: 'production',
entry: { // 多入口
home: './src/home',
topic: './src/topic',
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
},
plugins: [
new HtmlWebpackPlugin({ // home頁面
filename: 'home.html',
template: './template.html',
}),
new HtmlWebpackPlugin({ // topic頁面
filename: 'topic.html',
template: './template.html',
inject: true,
}),
],
...
}
複製程式碼
splitChunks
chunks:
- all: 不管檔案是動態還是非動態載入,統一將檔案分離。當頁面首次載入會引入所有的包
- async: 將非同步載入的檔案分離,首次一般不引入,到需要非同步引入的元件才會引入。
- initial:將非同步和非非同步的檔案分離,如果一個檔案被非同步引入也被非非同步引入,那它會被打包兩次(注意和all區別),用於分離頁面首次需要載入的包。
minSize: 檔案最小打包體積,單位byte,預設30000
比如說某個專案下有三個入口檔案,a.js和b.js和c.js都是100byte,當我們將minSize設定為301,那麼webpack就會將他們打包成一個包,不會將他們拆分成為多個包。
automaticNameDelimiter: 連線符
假設我們生成了一個公用檔名字叫vendor,a.js,和b.js都依賴他,並且我們設定的連線符是"~"那麼,最終生成的就是 vendor~a~b.js
maxInitialRequests 入口點處的最大並行請求數,預設為3
如果我們設定為1,那麼每個入口檔案就只會打包成為一個檔案
maxAsyncRequests 最大非同步請求數量,預設5
如果我們設定為1,那麼每個入口檔案就只會打包成為一個檔案
優先順序關係
maxInitialRequest / maxAsyncRequests <maxSize <minSize。
cacheGroups 定製分割包的規則
test可以配置正則和寫入function作為打包規則。其他屬性均可繼承splitChunks,這裡必須說一下priority,設定包的打包優先順序,非常重要!(後面結合實踐)
minChunks
最少引入的次數
實踐
我們以一個最簡單的配置開始,將公共程式碼打包出來
splitChunks: {
chunks: 'all', // initial、async和all
minSize: 30000, // 形成一個新程式碼塊最小的體積
maxAsyncRequests: 5, // 按需載入時候最大的並行請求數
maxInitialRequests: 3, // 最大初始化請求數
automaticNameDelimiter: '~', // 打包分割符
name: true,
cacheGroups: {
vendors: { // 打包兩個頁面的公共程式碼
minChunks: 2, // 引入兩次及以上被打包
name: 'vendors', // 分離包的名字
chunks: 'all'
},
}
},
複製程式碼
兩個入口檔案的公共程式碼被打包到vendor資料夾下面,包括echarts d3 amcharts等一些三方包和src下的公共程式碼。
這當然不是我們想要的結果!存在以下問題:
-
其實當我們進入網站,一般第一步都是進入一個登陸頁面,需要的只是專案的基本框架程式碼,例如react,react-dom.antd等,我們可以用all(或者initial)將它們單獨打包,作為首頁必須載入的包
-
我們打包的公共包,首次載入頁面的時候,只想把同步載入的載入進來,所以需要一個同步的Common包
-
像echarts,d3,以及一些src下面一些非同步載入的包,將它們利用async將打包成一個獨立非同步載入包
我們修改cacheGroups為:
cacheGroups: {
vendors: { // 專案基本框架等
chunks: 'all',
test: /(react|react-dom|react-dom-router|babel-polyfill|mobx)/,
priority: 100,
name: 'vendors',
},
'async-commons': { // 非同步載入公共包、元件等
chunks: 'async',
minChunks: 2,
name: 'async-commons',
priority: 90,
},
commons: { // 其他同步載入公共包
chunks: 'all',
minChunks: 2,
name: 'commons',
priority: 80,
},
}
複製程式碼
這次webpack幫我們打出來的包主要有:
-
async-common:是兩個入口檔案都非同步載入的三方包和利用react-loader做的懶載入程式碼,有echarts,d3等
-
vendors: 包括react,react-dom,antd等
-
commons: 引用超過兩次的同步程式碼
這裡說兩個需要注意的地方:
-
注意這裡我們priority的設定,vendors>async-commons>commons,我們首先將react,react-dom等優先打包出來,然後再打包公共部分,如果將vendors的優先順序設定小於兩個Common的優先順序,那麼react,react-dom將會打包到common包,將不會再生成vendors包。
-
如果我們這裡將commons的配置去掉,將會生成一個topic~home的包,我們配置了async-common提取出非同步載入的公共包後,將會預設將同步載入的公共包打包生成以automaticNameDelimiter連線符‘~’生成的名字'topic~home'包,內容其實和commons包內容完全一樣,
ok!按照我們的要求,這樣首次頁面載入只會引入vendors,commons包,而不會引入async-common包,還是挺棒的!追求更精緻的我們,再認真想想,是不是還可以做一些更好的優化?
到目前為止我們打包檔案的打包是這樣的:
用gzip壓縮後,最大的async-common包有391kb。公司說最近因為一些狀況,佈置到生產後速度慢的時候,有時候只能有20kb/s的下載速度==。。。。於是繼續split!分析一下:
-
async-common中包含了自己寫的src元件和第三方元件
-
async-common中比較大是echarts,zrender(echarts引入)和d3,結合專案來說,只有部分頁面我們需要echarts(d3同),所以我們可以考慮將d3和echarts這兩個比較大的包提取出來,等到某個頁面需要的時候,再讓其非同步載入,這樣就大大減小了async-common的體積了。
修改
cacheGroups: {
vendors: { // 基本框架
chunks: 'all',
test: /(react|react-dom|react-dom-router|babel-polyfill|mobx)/,
priority: 100,
name: 'vendors',
},
d3Venodr: { // 將體積較大的d3單獨提取包,指定頁面需要的時候再非同步載入
test: /d3/,
priority: 100, // 設定高於async-commons,避免打包到async-common中
name: 'd3Venodr',
chunks: 'async'
},
echartsVenodr: { // 非同步載入echarts包
test: /(echarts|zrender)/,
priority: 100, // 高於async-commons優先順序
name: 'echartsVenodr',
chunks: 'async'
},
'async-commons': { // 其餘非同步載入包
chunks: 'async',
minChunks: 2,
name: 'async-commons',
priority: 90,
},
commons: { // 其餘同步載入包
chunks: 'all',
minChunks: 2,
name: 'commons',
priority: 80,
},
}
複製程式碼
當然,每次修改後,需要在htmlWebpackPlugin中配置chunk需要的包
plugins: [
new HtmlWebpackPlugin({ // home頁面
filename: 'home.html',
template: './template.html',
chunks: ['vendors', 'commons', 'home'],
}),
new HtmlWebpackPlugin({ // topic頁面
filename: 'topic.html',
template: './template.html',
chunks: ['vendors', 'commons', 'topic'],
}),
],
複製程式碼
後期還做了其他的拆分和優化,大概最大的包保持在100k左右,當然也不建議拆的特別小,因為瀏覽器http1可能一次性支援6次下載檔案,太多可能會適得其反。大家可以根據自己的專案做不同的拆分方法,總而言之,就是為了讓專案更完美的線上上執行,給使用者更好的體驗~