webpack 源碼解讀(3)--CommonsChunkPlugin

CommonsChunkPlugin應該是用得最多的幾個插件之一了,主要是用來提取公共代碼,將指定的代碼打入單獨的入口文件。

之前的一篇文章

Shen:webpack 源碼解讀(2)--編譯流程zhuanlan.zhihu.com圖標

我已經講過,webpack打包最後是將入口chunk塊生成為assetasset就是最終生成的單獨文件,CommonsChunkPlugin這個插件其實也就是準備這個文件,這裡我就不講這個插件是怎麼用的了,只簡單說下它的工作流程。

下面是CommonsChunkPlugin的核心部分代碼(有刪減):

// 這個插件主要是在"optimize-chunks"這個鉤子函數觸發時候執行的compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], (chunks) => { /** * 執行時候首先會通過getTargetChunks得到需要單獨打包的chunk塊,也就是最後代碼單獨 * 提取出來放置的那一個文件,這裡會從chunks(所有的chunk)裡面找到需要提取的入口文件, * 如果沒有的話則新生成一個chunk文件,詳情可以看getTargetChunks方法源碼 */ const targetChunks = this.getTargetChunks(chunks, compilation, this.chunkNames, this.children, this.async); // 對需要提取的chunk進行遍歷 targetChunks.forEach((targetChunk, idx) => { /** * 先根據 this.selectedChunks(也就是CommonsChunkPlugin插件配置時候的chunks屬性) * 找到哪些chunk入口文件中的module需要提取到一起,參數中的chunks是全部的, * selectedChunks配置了多少chunks,getAffectedChunks就會從chunks中返回多少 * 需要提取的的chunk。如果不配置CommonsChunkPlugin的chunks屬性,則 * getAffectedChunks會返回所有的chunk,也就是從所有的入口文件中進行提取 */ const affectedChunks = this.getAffectedChunks(compilation, chunks, targetChunk, targetChunks, idx, this.selectedChunks, this.async, this.children); /** * 由於每個chunk文件都包含多個module子模塊,所以getExtractableModules方法會獲取需要 * 提取的modules模塊,是否需要提取的依據就是minChunks這個參數,minChunks默認值是數 * 字2,也就是默認使用兩次以上的module模塊都進行提取,實際上minChunks也可以傳入自定義 * 方法,方法內部寫判斷條件,符合條件就返回true,表明是需要提取的塊 */ const extractableModules = this.getExtractableModules(this.minChunks, affectedChunks, targetChunk); // 最小模塊個數限制,如果傳入了此參數,提取的模塊數量小於此數字,則不進行提取 if(this.minSize) { const modulesSize = this.calculateModulesSize(extractableModules); if (modulesSize < this.minSize) return; } // 將需要提取的module模塊添加到這個單獨提取chunk塊中 this.addExtractedModulesToTargetChunk(targetChunk, extractableModules); // 新生成的chunkl添加到entrypoint入口文件中 this.makeTargetChunkParentOfAffectedChunks(affectedChunks, targetChunk); }); return true;});

個人覺得CommonsChunkPlugin這個插件最方便擴展的就是minChunks參數,可以根據實際需要非常方便的進行打包分配

可以參見官網的例子:

new webpack.optimize.CommonsChunkPlugin({ name: "my-single-lib-chunk", filename: "my-single-lib-chunk.js", minChunks: function(module, count) { // 如果模塊是一個路徑,而且在路徑中有 "somelib" 這個名字出現, // 而且它還被三個不同的 chunks/入口chunk 所使用,那請將它拆分到 // 另一個分開的 chunk 中,chunk 的 keyname 是 "my-single-lib-chunk",而文件名 // 是 "my-single-lib-chunk.js" return module.resource && (/somelib/).test(module.resource) && count === 3; }});

最後說一個我一直不太了解的屬性names,之前一直不了解是怎麼用,看了源碼之後才大概了解我們先來看看官網的說明

names: string[],// 這是 common chunk 的名稱。已經存在的 chunk 可以通過傳入一個已存在的// chunk 名稱而被選擇。如果一個字元串數組被傳入,這相當於插件針對每個 chunk 名被多次調用// 如果該選項被忽略,同時 `options.async` 或者 `options.children` 被設置,所有// 的 chunk 都會被使用, 否則 `options.filename` 會用於作為 chunk 名。

開始一直不明白是啥意思,後面看了下他初始化的地方

const chunkNames = options.name || options.names ? [].concat(options.name || options.names) : undefined;return { chunkNames: chunkNames,

原來name不傳的時候names是當做了chunkNames使用了,打個比方:

new webpack.optimize.CommonsChunkPlugin({ names: ["common/a","common/b"], // 這裡filename的可以和webpack.config.js的配置中的output使用一樣的佔位符 filename: "[name].js", minChunks: function(module, count) { return true }});

這個地方names傳了一個有2個元素的數組,所以getTargetChunks方法生成的也會是兩個targetChunk,這就是為啥上面的源碼中明明CommonsChunkPlugin插件是將共用代碼提取到一個文件裡面,生成一個targetChunk就好了,但是還要用targetChunks.forEach去遍歷

實際上,我們的CommonsChunkPlugin使用得當的話不但能進行文件代碼提取,還能進行文件分解,我們改一下上面配置中的minChunks,變成動態的條件,就會達到此效果,下面簡單寫個demo,雖然使用場景不多,但是大家能更好的理解names這個屬性傳參

/** * 以下配置可以將路徑長度為偶數的模塊和路徑長度為奇數的模塊分別 * 提取到common/a.js和common/b.js中,通常判斷條件第一次就會提取完 * 成,第二次基本上提取不到* 數據,但是這裡只是告訴一下大家names的用法 */new webpack.optimize.CommonsChunkPlugin({ names: ["common/a","common/b"], filename: "[name].js", minChunks: function(module, count) { return module.userRequest && module.userRequest.length%2 }});

推薦閱讀:

解決 React+Webpack 打包體積過大
Webpack工程化解決方案easywebpack
翻譯 | webpack2的入門手冊
Webpack 之 Loader 的使用
移動端網頁調試神器Eruda使用技巧

TAG:webpack | 前端工程化 | 源碼閱讀 |