webpack 二三事:如何讓 CommonsChunkPlugin() 發揮最大效益
本篇為譯文,原文出自 Sean T. Larkin 的 webpack bits: Getting the most out of the CommonsChunkPlugin()
webpack 核心團隊喜歡三不五時地在 Twitter 上和社群成員互動,並透過有趣且富啟發性的方式分享各種小知識。
這次的遊戲規則非常簡單:安裝 webpack-bundle-analyzer
為你的 bundles 產生絢麗的色彩圖並分享給我,webpack 團隊將協助辨認任何可能的潛在問題作為回報!
我們發現了什麼?
最常見的是程式碼重覆的狀況:同樣的函式庫、元件或程式碼重覆出現在多個同步或非同步載入的 bundles 中!
狀況一:多個 vendor bundles 存在重覆的程式碼
Swizec Teller 慷慨地分享了一份他的成果圖(事實上是從超過 8 到 9 個獨立的 single-page applications 產生的)。由於我們可以透過這個例子認識幾個有用的技巧,因此我從眾多結果中選擇了它作為範例。讓我們更詳細地檢視它:
即使不看實際設定的部份,我們也能先從這張圖中得到一些推論。
每個 single-page application 都使用了 new CommonsChunkPlugin()
指定它的 entry point 和 vendor,如此一來就會分別為 application 本身的程式碼與來自 node_modules 的模組產生各自的 bundles。這部份的設定有被提供出來:
Object.keys(activeApps)
.map(app => new webpack.optimize.CommonsChunkPlugin({
name: `${app}_vendor`,
chunks: [app],
minChunks: isVendor
}))
這邊的 activeApps
變數看起來包含了各自獨立的 entry points。
可以改善的區域
下面這些我圈起來的部份可以再做些改進。
「Meta」快取
從上面的圖中我們可以看出像是 momentjs、lodash 和 jquery 這類的大型函式庫被 6 個以上的 vendor bundles 使用。將所有 vendor 放到獨立的 bundle 中是個好策略,而我們也應該將同樣的方法套用在所有 vendor bundles 上。
我建議 Swizec 將下面這些設定放到 plugins array 的最後一段:
new webpack.optimize.CommonsChunkPlugin({
children: true,
minChunks: 6
})
這就像我們告訴 webpack:
嘿 webpack,查看一下所有的 chunks(包含產生出來的 vendor chunks)並把出現 6 次以上的模組移到另一個獨立的檔案中。
如你所見,這些模組全部被移到了另一個獨立的檔案中。最重要的是, Swizec 回報這個方法把整個 application 的大小減少了 17%!
狀況二:非同步載入的 chunks 存在重覆的 vendor 程式碼
這個程式碼重覆的情況對 application 整體的檔案大小來說並不是太嚴重,不過當你仔細觀察下圖時可以發現在每個非同步載入的 chunks 中都有 3 個相同的模組存在。
如上圖所示,這 2 到 3 個元件被總共 40 到 50 個非同步載入的 bundles 使用。我們要如何使用 CommonsChunkPlugin
解決這個問題?
建立非同步載入的 Commons Chunk
這個解決方法和狀況一的很類似,不過我們要把設定中的 async
屬性設定為 true
:
new webpack.optimize.CommonsChunkPlugin({
async: true,
children: true,
filename: "commonlazy.js"
});
同樣地,webpack 會掃描所有的 chunks 找出共通的模組。不過因為 async: true
的設定,只有 code splitting 產生的 bundles 才會成為目標。在沒有指定 minChunks
的情況下會使用預設值 3,所以 webpack 接收到的資訊如下:
嘿 webpack,查看一下所有一般的(也就是非同步載入的)chunks 並把所有重覆出現 3 次以上的模組變成另一個獨立的非同步載入 chunk。
這是成果:
現在非同步載入的 chunks 變得極小,所有重覆存在的程式碼都被移到了 commonlazy.js 中。因為這些 bundles 原本就不是很大,這樣的調整在使用者第二次造訪前其實並不會很明顯。不過因為已經把共同的模組獨立成可以快取的 chunk,對於每個非同步載入的 bundle 我們都可以省下更多的資料傳輸與載入時間。
更多控制手段:minChunks 函數
還有什麼辦法進行更精細的設定嗎?有些情況下你可能不想只產生單一個共用的 bundle,因為並不是每個 chunk 都會用到它。其實 minChunks
屬性是可以設定成函數的!!這就可以作為要將什麼模組加入 bundle 的「篩選條件」使用,舉例如下:
new webpack.optimize.CommonsChunkPlugin({
filename: "lodash-moment-shared-bundle.js",
minChunks: function(module, count) {
return module.resource && /lodash|moment/.test(module.resource) && count >= 3
}
})
這樣的設定表示:
嘿 webpack,如果找到重覆出現 3 次以上而且絕對路徑中有 lodash 或 moment 的模組,就把它們移動到獨立的 bundle 中。
同樣地你也可以設定 async: true
把它套用在非同步載入的 bundles 上!
更多更多的控制手段
透過 minChunks
我們就可以為特定 entries 和 bundles 建立更多可快取的 vendors 子集合,最後可能會產生類似這樣的設定:
function lodashMomentModuleFilter(module, count) {
return module.resource && /lodash|moment/.test(module.resource) && count >= 2;
}function immutableReactModuleFilter(module, count) {
return module.resource && /immutable|react/.test(module.resource) && count >=4
}new webpack.optimize.CommonsChunkPlugin({
filename: "lodash-moment-shared-bundle.js",
minChunks: lodashMomentModuleFilter
})new webpack.optimize.CommonsChunkPlugin({
filename: "immutable-react-shared-bundle.js",
minChunks: immutableReactModuleFilter
})
沒有所謂的萬靈丹!
CommonsChunkPlugin()
也許很強大,但要記得這裡的範例都是為了對應的 application 量身打造。在你將這些程式碼片段複製貼上前,記得聽聽 Sam Saccone 和 Paul Irish 的建議並先進行 MPDIA 確保你使用了正確的方法。
我要從哪裡找到更多範例?
這邊只是 CommonsChunkPlugin() 使用和設定的一些樣本,想要了解更多的話可以參考我們在 GitHub 上 webpack/webpack 核心 repo 的 /example
目錄!如果你有更多好點子的話,請儘管發 Pull Request 出來吧!