webpack 拾翠:充分利用 CommonsChunkPlugin()

檻外畸人發表於2017-04-11

webpack 核心團隊隔三差五地就會在 Twitter 上作一些寓教於樂的技術分享

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

這次的“遊戲規則”很簡單:安裝 webpack-bundle-analyzer,生成一張包含所有 bundles 資訊的酷炫圖片分享給我,然後 webpack 團隊會幫忙指出任何潛在的問題。

我們發現了什麼?

最常見的問題是程式碼重複:庫、元件、程式碼在多個(同步的、非同步的)bundles 中重複出現。

案例一:很多重複程式碼的 vendor bundles

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

Swizec Teller 分享了一個構建圖(實際上是對 8-9 個獨立單頁應用的構建)。在眾多例子中我決定選擇這一個,因為我們可以從中學到很多技術,下面讓我們來仔細分析一下:

webpack 拾翠:充分利用 CommonsChunkPlugin()

距離 “FoamTree” 圖示最近的是應用本身的程式碼,而其他所有 node_modules 的程式碼則是左邊那些以 "_vendor.js" 結尾的。

單從這幅圖(不需要看實際配置檔案)中我們就能推斷出很多事情。

每個單頁應用都運用了一個 new CommonsChunkPlugin ,並以其 entry 和 vendor 程式碼為目標。這會生成兩個 bundles,一個只包含 node_modules 裡面的程式碼,另一個則只包含應用本身的程式碼。(Swizec Teller)甚至還提供了部分配置資訊:

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

Object.keys(activeApps)
  .map(app => new webpack.optimize.CommonsChunkPlugin({
    name: `${app}_vendor`,
    chunks: [app],
    minChunks: isVendor
  }))複製程式碼

其中 activeApps 變數很可能是用來表示獨立入口點的。

可以優化的地方

下面幾個畫圈的是可以優化的地方。

webpack 拾翠:充分利用 CommonsChunkPlugin()

“Meta” 快取

從上圖可以看出,許多大型程式碼庫(例如 momentjs、lodash、jquery 等)同時被 6 個(甚至更多) bundles 用到了。將所有 vendors 打包到一個獨立 bundle 中的策略是很好的,但其實對所有 vendor bundles 也應該採取同樣的策略。

我建議 Swizec 將如下外掛新增到外掛陣列的末尾

new webpack.optimize.CommonsChunkPlugin({
  children: true, 
  minChunks: 6
})複製程式碼

這是在告訴 webpack:

嘿 webpack,請檢查所有的 chunks(包括那些由 webpack 生成的 vendor chunks),找出那些在 6個及6個以上 chunks 中都出現過的模組,並將其移到一個獨立的檔案中。

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

如你所見,現在所有符合要求的模組都被抽離到一個獨立的檔案中,Swizec 指出這個應用程式大小降低了 17%。

案例二:非同步 chunks 中的重複 vendors

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

就整體程式碼體積來說,這種數量的重複並不嚴重;但是,如果你看到下面這張完整大圖,你就會發現每一個非同步 chunk 中都有 3 個一模一樣的模組。

webpack 拾翠:充分利用 CommonsChunkPlugin()

非同步 chunks 是指那些檔名中包含 "[number].[number].js" 的 chunk。

如上圖所示,四五十個非同步 bundles 都用到了兩三個同樣的元件,我們該如何利用 CommonsChunkPlugin 來解決此問題呢?

建立一個非同步 Commons Chunk

解決方法和第一個案例中的類似,但是需要將配置選項中的 async 屬性設為 true,程式碼如下:

new webpack.optimize.CommonsChunkPlugin({
  async: true, 
  children: true, 
  filename: "commonlazy.js"
});複製程式碼

類似地 —— webpack 會掃描所有 chunks 並檢查公共模組。由於設定了 async: true,只有程式碼拆分的 bundles 會被掃描。因為我們並沒有指明 minChunks 的值,所以 webpack 會取其預設值 3。綜上,上述程式碼的含義是:

嘿 webpack,請檢查所有的普通(即懶載入的)chunks,如果某個模組出現在了 3 個或 3 個以上的 chunks 中,就將其分離到一個獨立的非同步公共 chunk 中去。

效果如下圖所示:

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

現在非同步 chunks 都非常的小,並且所有程式碼都被聚合到 commonlazy.js 檔案中去了。因為這些 bundles 本來就很小了, 首次訪問可能都察覺不到程式碼體積的變化。現在,每一個程式碼拆分的 bundle 所需攜帶的資料更少了;而且,通過將這些公共模組放到一個獨立可快取的 chunk 中,我們節省了使用者載入時間,減少了需要傳輸的資料量(data consumption)。

更多控制:minChunks 函式

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

那如果你想要跟多的控制權呢?某些情況下你可能並不想要一個單獨的共享 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,如果你發現某個模組的絕對路徑和 lodash 或 momentjs 相匹配並且出現在了 3 個(或 3 個以上)獨立的 entries/chunks 中,請將其抽取到一個獨立的 bundle 中去。

通過設定 async: true,你也可以將此方法應用到非同步 bundles 中。

更多更多控制

webpack 拾翠:充分利用 CommonsChunkPlugin()
Markdown

有了這種 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() 固然很強大,但要記住本文中的例子都是針對特定應用的。因此,在複製-貼上這些程式碼片段之前,請先聽聽 Sam SacconePaul IrishMPDIA 的建議,避免用錯了方法。

webpack 拾翠:充分利用 CommonsChunkPlugin()

webpack 拾翠:充分利用 CommonsChunkPlugin()

在應用解決方法之前,一定要理解方法背後的思路!

哪裡還有更多例子?

上述只是 CommonsChunkPlugin() 的部分用例,更多資源請參考我們 webpack/webpack core GitHub 倉庫中的 [/examples](https://github.com/webpack/webpack/tree/master/examples) 目錄。如果你還有其他好想法,歡迎 Pull Request

沒時間貢獻程式碼?希望以其他方式做貢獻?向我們的 open collective 捐款,即刻成為贊助商。Open Collective 不僅為核心團隊提供支援,同時也幫助那些為提升我們社群質量而花費了大量寶貴的空閒時間的貢獻者們!❤


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章