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

Chia-Wei Li
Jan 1, 2018 · 8 min read

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 出來吧!

Frochu

Frochu — Frontend Ochu ,程式碼的黑手,親手實作的前端知識推動者

Thanks to Amdis Liu

Chia-Wei Li

Written by

Full stack and mobile developer

Frochu

Frochu

Frochu — Frontend Ochu ,程式碼的黑手,親手實作的前端知識推動者

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade