本文大約1500字,看完本文大概需要10分鐘,動手嘗試需要1小時,如有錯誤,請指正。
初衷
webpack4出了兩個月,發現大家包括我對splitChunk的使用都還是在摸索階段。我也看了挺多別人的配置demo,都覺得不太滿意或者沒得到太好的解惑,issue 下面的問題也沒什麼人回覆,只能自己操作了,順便記錄下來,如果大家有更好的,歡迎評論區留下地址。
常用引數
- minSize(預設是30000):形成一個新程式碼塊最小的體積
- minChunks(預設是1):在分割之前,這個程式碼塊最小應該被引用的次數(譯註:保證程式碼塊複用性,預設配置的策略是不需要多次引用也可以被分割)
- maxInitialRequests(預設是3):一個入口最大的並行請求數
- maxAsyncRequests(預設是5):按需載入時候最大的並行請求數。
- chunks (預設是async) :initial、async和all
- test: 用於控制哪些模組被這個快取組匹配到。原封不動傳遞出去的話,它預設會選擇所有的模組。可以傳遞的值型別:RegExp、String和Function
- name(打包的chunks的名字):字串或者函式(函式可以根據條件自定義名字)
- priority :快取組打包的先後優先順序。
如果你對這些配置還是不熟悉的話,一拉到底,看看文件
正文
先總覽一下所有配置,後續會根據demo跑一遍常見的需求。
optimization: {
splitChunks: {
chunks: "async", // 必須三選一: "initial" | "all"(推薦) | "async" (預設就是async)
minSize: 30000, // 最小尺寸,30000
minChunks: 1, // 最小 chunk ,預設1
maxAsyncRequests: 5, // 最大非同步請求數, 預設5
maxInitialRequests : 3, // 最大初始化請求書,預設3
automaticNameDelimiter: `~`,// 打包分隔符
name: function(){}, // 打包後的名稱,此選項可接收 function
cacheGroups:{ // 這裡開始設定快取的 chunks
priority: 0, // 快取組優先順序
vendor: { // key 為entry中定義的 入口名稱
chunks: "initial", // 必須三選一: "initial" | "all" | "async"(預設就是async)
test: /react|lodash/, // 正則規則驗證,如果符合就提取 chunk
name: "vendor", // 要快取的 分隔出來的 chunk 名稱
minSize: 30000,
minChunks: 1,
enforce: true,
maxAsyncRequests: 5, // 最大非同步請求數, 預設1
maxInitialRequests : 3, // 最大初始化請求書,預設1
reuseExistingChunk: true // 可設定是否重用該chunk
}
}
}
},
複製程式碼
接下來看看第一個例子
entry: {
pageA: "./pageA", // 引用utility1.js utility2.js
pageB: "./pageB", // 引用utility2.js utility3.js
pageC: "./pageC" // 引用utility2.js utility3.js
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0 // This is example is too small to create commons chunks
}
}
}
},
複製程式碼
結果如圖,一切都很正常 commons~pageA~pageB~pageC.js 檔案就是utility2.js
commons~pageB~pageC.js,根據上述程式碼,這裡的utility2被引用了三次,首先就被抽離了commons~pageA~pageB~pageC.js,然後utility3被引用了兩次就放到了commons~pageB~pageC.js,最後只剩下被引用一次的utility1.js,就直接放到了pageA.js裡面,如果這裡的utility1.js的也是兩次,他還是會新建一個chunk放進去,而不是合併到commons~pageB~pageC.js,除非同入口引用才會合併。
mpageA.js pageB.js pageC.js
這裡有個地方是需要優化一下的,就是pageA.js pageB.js pageC.js的程式碼不多,但是打出來的包很大,肯定是一些webpack的執行檔案,直接加上
runtimeChunk
runtimeChunk: "single"
// 等價於
runtimeChunk: {
name: "manifest"
}
複製程式碼
現在就好了
引用第三方模組 pageA引用vue.js pageB引用react react-dom
vendor: {
test: /node_modules/,
chunks: "initial",
name: "vendor",
priority: 10,
enforce: true
}
複製程式碼
但是,這樣子的話,會把pageA pageB pageC所有的庫都打包到一起vendor.js
假如我想拆分這個vendor.js為pageA-vendor.js pageB-vendor.js怎麼辦,我試了很久,試出一個最簡單的辦法,去掉手動的vendor,讓外掛自動處理。
splitChunks: {
chunks: "all",
cacheGroups: {
commons: {
chunks: "initial",
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0 // This is example is too small to create commons chunks
}
}
},
複製程式碼
後來,我把webpack mode改成production後,發現不管用了,同樣的配置,在生產模式下,打包出來的東西有點匪夷所思,vendor-pageB.js被合併到了pageB.js裡面了。
後來我折騰了好久也分析不出來為什麼,自己折騰出來一種方式,還是老子手動來吧,自動化一邊去
commons: {
chunks: "initial",
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0 // This is example is too small to create commons chunks
},
`vendor-pageA`: {
test: /vue/, // 直接使用 test 來做路徑匹配
chunks: "initial",
name: "vendor-pageA",
enforce: true,
},
`vendor-pageB`: {
test: /react/, // 直接使用 test 來做路徑匹配
chunks: "initial",
name: "vendor-pageB",
enforce: true,
},
複製程式碼
成功打包出來了自己想要的東西。
動態引入
動態引入大家應該都不陌生,就是大家所說的懶載入,直接在pageA和pageB頁面裡動態引入common-async.js,在這裡我先說說,splitChunk應該是可以自動化處理類似commonChunk裡的async,child等情況的。
import(/* webpackChunkName: "common-async.js" */"./common-async").then(common => {
console.log(common);
})
複製程式碼
還不錯,成功打包出來了
這時候再試試,在這個common-async.js裡面在引入共同的程式碼f.js,看看會不會重複打包
f.js成功的被抽離出來了,其他檔案也沒有被重複打包,挺好的。
注意的地方
- cacheGroups 會繼承和覆蓋splitChunks的配置項,但是test、priorty和reuseExistingChunk只能用於配置快取組。。
- cacheGroups 裡的每一項最好都要加上chunks引數,不然可能打包不出來你想要的東西。
- minSize 預設是30KB(注意這個體積是壓縮之前的)在小於30kb的情況下一定要設定一個值,否則也可能打包不出來你想要的東西,而且這東西要加在cacheGroups裡面。
- priority 在某些情況下,還是挺有用的,可以設定打包chunks的優先順序。
上面的例子裡面配置了一個commons,這裡的name可以自己設定,也可以不設定,我是沒設定的,你可以試試設定了是什麼樣子的,然後你就會明白這個name其實在某些情況下還是不設定的比較好。
commons: {
chunks: "initial",
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0 ,
name: "commons"
},
複製程式碼
總結
可見,splitChunk在懶載入方面,自動化處理的挺不錯的,但在多頁面的配置(根據不同的頁面抽離不同的vendor)裡,他因為有自己的一套優化策略,往往會得到不是我們想要的輸出。這篇文章只是我初步的一些常識,我還沒有深入去看原始碼,後續有空可能會補上splitChunk原始碼分析,到時候就更清晰了