webpack 二三事:如何讓 CommonsChunkPlugin() 發揮最大效益

本篇為譯文,原文出自 Sean T. Larkinwebpack bits: Getting the most out of the CommonsChunkPlugin()

Chia-Wei Li
Frochu
8 min readJan 1, 2018

--

webpack 核心團隊喜歡三不五時地在 Twitter 上和社群成員互動,並透過有趣且富啟發性的方式分享各種小知識。

這次的遊戲規則非常簡單:安裝 webpack-bundle-analyzer 為你的 bundles 產生絢麗的色彩圖並分享給我,webpack 團隊將協助辨認任何可能的潛在問題作為回報!

我們發現了什麼?

最常見的是程式碼重覆的狀況:同樣的函式庫、元件或程式碼重覆出現在多個同步或非同步載入的 bundles 中!

狀況一:多個 vendor bundles 存在重覆的程式碼

感謝 Swizec 讓我分享這個經典的例子

Swizec Teller 慷慨地分享了一份他的成果圖(事實上是從超過 8 到 9 個獨立的 single-page applications 產生的)。由於我們可以透過這個例子認識幾個有用的技巧,因此我從眾多結果中選擇了它作為範例。讓我們更詳細地檢視它:

靠近「FoamTree」圖示的部份是 application 本身的程式碼,而左半部以「_vendor.js」結尾的部份則來自 node_modules

即使不看實際設定的部份,我們也能先從這張圖中得到一些推論。

每個 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 次以上的模組移到另一個獨立的檔案中。

看起來這個獨立檔案的名稱是「manifest.js」?

如你所見,這些模組全部被移到了另一個獨立的檔案中。最重要的是, Swizec 回報這個方法把整個 application 的大小減少了 17%!

狀況二:非同步載入的 chunks 存在重覆的 vendor 程式碼

這是個讓人印象深刻的 code splitting 成果,看看那漂亮的顏色 💓

這個程式碼重覆的情況對 application 整體的檔案大小來說並不是太嚴重,不過當你仔細觀察下圖時可以發現在每個非同步載入的 chunks 中都有 3 個相同的模組存在。

非同步載入的 chunks 檔案名稱為「[數字].[數字].js」

如上圖所示,這 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。

這是成果:

這裡還能把 minChunks 的數值調高來產生更小的 commonlazy.js

現在非同步載入的 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 SacconePaul Irish 的建議並先進行 MPDIA 確保你使用了正確的方法。

套用解法前確實弄懂你要做的事!

我要從哪裡找到更多範例?

這邊只是 CommonsChunkPlugin() 使用和設定的一些樣本,想要了解更多的話可以參考我們在 GitHub 上 webpack/webpack 核心 repo 的 /example 目錄!如果你有更多好點子的話,請儘管發 Pull Request 出來吧!

--

--