CommonsChunkPlugin主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle文件或者按需加載的bundle文件體積過大,從而致使加載時間過長,着實是優化的一把利器。node
先來講一下各類教程以及文檔中CommonsChunkPlugin說起到chunk有哪幾種,主要有如下三種:jquery
children和async屬於異步中的應用,放在了最後講解。webpack
可能這麼說,你們會雲裏霧裏,下面用demo來檢驗上面的屬性。git
如下幾個demo主要是測試如下幾種狀況:github
項目初始結構,後面打包後會生成dist目錄:web
src目錄下各個文件內容都很簡潔的,以下:npm
common.js export const common = 'common file'; first.js import {common} from './common'; import $ from 'jquery'; console.log($,`first ${common}`); second.js import {common} from './common'; import $ from 'jquery'; console.log($,`second ${common}`);
package.json文件:json
{ "name": "test", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "rimraf dist && webpack" }, "author": "", "license": "ISC", "devDependencies": { "rimraf": "^2.6.2", "webpack": "^3.10.0", "webpack-dev-server": "^2.10.1" }, "dependencies": { "jquery": "^3.2.1" } }
webpack.config.js:segmentfault
const path = require("path"); const webpack = require("webpack"); const config = { entry: { first: './src/first.js', second: './src/second.js' }, output: { path: path.resolve(__dirname,'./dist'), filename: '[name].js' }, } module.exports = config;
接着在命令行npm run build,此時項目中多了dist目錄:瀏覽器
再來查看一下命令行中webpack的打包信息:
查看first.js和second.js,會發現共同引用的common.js文件和jquery都被打包進去了,這確定不合理,公共模塊重複打包,體積過大。
這時候修改webpack.config.js新增一個入口文件vendor和CommonsChunkPlugin插件進行公共模塊的提取:
const path = require("path"); const webpack = require("webpack"); const packagejson = require("./package.json"); const config = { entry: { first: './src/first.js', second: './src/second.js', vendor: Object.keys(packagejson.dependencies)//獲取生產環境依賴的庫 }, output: { path: path.resolve(__dirname,'./dist'), filename: '[name].js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[name].js' }), ] } module.exports = config;
查看dist目錄下,新增了一個vendor.js的文件:
再來查看一下命令行中webpack的打包信息:
經過查看vendor.js文件,發現first.js和second.js文件中依賴的jquery和common.js都被打包進vendor.js中,同時還有webpack的運行文件。總的來講,咱們初步的目的達到,提取公共模塊,可是它們都在同一個文件中。
到這裏,確定有人但願自家的vendor.js純白無瑕,只包含第三方庫,不包含自定義的公共模塊和webpack運行文件,又或者但願包含第三方庫和公共模塊,不包含webpack運行文件。
其實,這種想法是對,特別是分離出webpack運行文件,由於每次打包webpack運行文件都會變,若是你不分離出webpack運行文件,每次打包生成vendor.js對應的哈希值都會變化,致使vendor.js改變,但實際上你的第三方庫實際上是沒有變,然而瀏覽器會認爲你原來緩存的vendor.js就失效,要從新去服務器中獲取,其實只是webpack運行文件變化而已,就要人家從新加載,好冤啊~
OK,接下來就針對這種狀況來測試。
這裏咱們分兩步走:
先來抽離webpack運行文件,修改webpack配置文件:
plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor','runtime'], filename: '[name].js' }), ]
其實上面這段代碼,等價於下面這段:
plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[name].js' }), new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', filename: '[name].js', chunks: ['vendor'] }), ]
上面兩段抽離webpack運行文件代碼的意思是建立一個名爲runtime的commons chunk進行webpack運行文件的抽離,其中source chunks是vendor.js。
查看dist目錄下,新增了一個runtime.js的文件,其實就是webpack的運行文件:
再來查看一下命令行中webpack的打包信息,你會發現vendor.js的體積已經減少,說明已經把webpack運行文件提取出來了:
但是,vendor.js中還有自定義的公共模塊common.js,人家只想vendor.js擁有項目依賴的第三方庫而已(這裏是jquery),這個時候把minChunks這個屬性引進來。
minChunks能夠設置爲數字、函數和Infinity,默認值是2,並非官方文檔說的入口文件的數量,下面解釋下minChunks含義:
要在vendor.js中把第三方庫單獨抽離出來,上面也說到了有兩種方法。
第一種方法minChunks設爲Infinity,修改webpack配置文件以下:
plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor','runtime'], filename: '[name].js', minChunks: Infinity }), new webpack.optimize.CommonsChunkPlugin({ name: 'common', filename: '[name].js', chunks: ['first','second']//從first.js和second.js中抽取commons chunk }), ]
查看dist目錄下,新增了一個common.js的文件:
再來查看一下命令行中webpack的打包信息,自定義的公共模塊分離出來:
這時候的vendor.js就純白無瑕,只包含第三方庫文件,common.js就是自定義的公共模塊,runtime.js就是webpack的運行文件。
第二種方法把它們分離開來,就是利用minChunks做爲函數的時候,說一下minChunks做爲函數兩個參數的含義:
minChunks做爲函數會遍歷每個入口文件及其依賴的模塊,返回一個布爾值,爲true表明當前正在處理的文件(module.resource)合併到commons chunk中,爲false則不合並。
繼續修改咱們的webpack配置文件,把vendor入口文件註釋掉,用minChunks做爲函數實現vendor只包含第三方庫,達到和上面同樣的效果:
const config = { entry: { first: './src/first.js', second: './src/second.js', //vendor: Object.keys(packagejson.dependencies)//獲取生產環境依賴的庫 }, output: { path: path.resolve(__dirname,'./dist'), filename: '[name].js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[name].js', minChunks: function (module,count) { console.log(module.resource,`引用次數${count}`); //"有正在處理文件" + "這個文件是 .js 後綴" + "這個文件是在 node_modules 中" return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, './node_modules')) === 0 ) } }), new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', filename: '[name].js', chunks: ['vendor'] }), ] }
上面的代碼其實就是生成一個叫作vendor的commons chunk,那麼有哪些模塊會被加入到vendor中呢?就對入口文件及其依賴的模塊進行遍歷,若是該模塊是js文件而且在node_modules中,就會加入到vendor當中,其實這也是一種讓vendor只保留第三方庫的辦法。
再來查看一下命令行中webpack的打包信息:
你會發現,和上面minChunks設爲Infinity的結果是一致的。
這兩個屬性主要是在code split(代碼分割)和異步加載當中應用。
children
修改webpack配置文件,增長chunkFilename,以下:
output: { ........... chunkFilename: "[name].[hash:5].chunk.js", }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor','runtime'], filename: '[name].js', minChunks: Infinity }), new webpack.optimize.CommonsChunkPlugin({ children: true, async: 'children-async' }) ]
chunkFilename用來指定異步加載的模塊名字,異步加載模塊中的共同引用到的模塊就會被合併到async中指定名字,上面就是children-async。
修改爲異步截圖出來太麻煩了,就簡單說明一下:first和second是異步加載模塊,同時它們共同引用了common.js這個模塊,若是你不設置這一步:
new webpack.optimize.CommonsChunkPlugin({ children: true, async: 'children-async' })
那麼共同引用的common.js都被打包進各自的模塊當中,就重複打包了。
OK,你設置以後,也得看children的臉色怎麼來劃分:
先來講一下哈希值的不一樣:
因此,在生產環境,要把文件名改爲'[name].[chunkhash]',最大限度的利用瀏覽器緩存。
最後,寫這篇文章,本身測試了不少demo,固然不可能所有貼上,但仍是但願本身多動手測試如下,真的坑中帶坑。
也參考了不少文章: