CommonsChunkPlugin
主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle
文件或者按需加載的bundle
文件體積過大,從而致使加載時間過長,着實是優化的一把利器。javascript
先來講一下各類教程以及文檔中CommonsChunkPlugin
說起到chunk
有哪幾種,主要有如下三種:java
webpack
當中配置的入口文件(entry)
是chunk
,能夠理解爲entry chunk
code split
(代碼分割)出來的也是chunk
,能夠理解爲children chunk
CommonsChunkPlugin
建立出來的文件也是chunk
,能夠理解爲commons chunk
name
:能夠是已經存在的chunk
(通常指入口文件)對應的name
,那麼就會把公共模塊代碼合併到這個chunk
上;不然,會建立名字爲name
的commons chunk
進行合併* filename
:指定commons chunk
的文件名
* chunks
:指定source chunk
,即指定從哪些chunk
當中去找公共模塊,省略該選項的時候,默認就是entry chunks
* minChunks
:既能夠是數字,也能夠是函數,還能夠是Infinity
,具體用法和區別下面會說node
children
和async
屬於異步中的應用,放在了最後講解。jquery
可能這麼說,你們會雲裏霧裏,下面用demo
來檢驗上面的屬性。webpack
如下幾個demo
主要是測試如下幾種狀況:git
項目初始結構,後面打包後會生成dist
目錄:
github
src目錄下各個文件內容都很簡潔的,以下:web
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文件:npm
{ "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:json
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
運行文件
接着單獨抽離第三方庫和自定義公共模塊,這裏利用minChunks
有兩種方法能夠完成,日後看就知道了
這裏解釋一下什麼是webpack
運行文件:
/******/ (function(modules) { // webpackBootstrap /******/ // install a JSONP callback for chunk loading /******/ var parentJsonpFunction = window["webpackJsonp"]; /******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { /******/ // add "moreModules" to the modules object, /******/ // then flag all "chunkIds" as loaded and fire callback /******/ var moduleId, chunkId, i = 0, resolves = [], result; /******/ for(;i < chunkIds.length; i++) { /******/ chunkId = chunkIds[i]; /******/ if(installedChunks[chunkId]) { /******/ resolves.push(installedChunks[chunkId][0]); /******/ } /******/ installedChunks[chunkId] = 0; /******/ } /******/ for(moduleId in moreModules) { /******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { /******/ modules[moduleId] = moreModules[moduleId]; /******/ } /******/ } /******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules); /******/ while(resolves.length) { /******/ resolves.shift()(); /******/ } /******/ if(executeModules) { /******/ for(i=0; i < executeModules.length; i++) { /******/ result = __webpack_require__(__webpack_require__.s = executeModules[i]); /******/ } /******/ } /******/ return result; /******/ }; /******/ /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // objects to store loaded and loading chunks /******/ var installedChunks = { /******/ 5: 0 /******/ }; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks /******/ __webpack_require__.e = function requireEnsure(chunkId) { /******/ var installedChunkData = installedChunks[chunkId]; /******/ if(installedChunkData === 0) { /******/ return new Promise(function(resolve) { resolve(); }); /******/ } /******/ /******/ // a Promise means "currently loading". /******/ if(installedChunkData) { /******/ return installedChunkData[2]; /******/ } /******/ /******/ // setup Promise in chunk cache /******/ var promise = new Promise(function(resolve, reject) { /******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; /******/ }); /******/ installedChunkData[2] = promise; /******/ /******/ // start chunk loading /******/ var head = document.getElementsByTagName('head')[0]; /******/ var script = document.createElement('script'); /******/ script.type = "text/javascript"; /******/ script.charset = 'utf-8'; /******/ script.async = true; /******/ script.timeout = 120000; /******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } /******/ script.src = __webpack_require__.p + "static/js/" + ({"3":"comC"}[chunkId]||chunkId) + "." + chunkId + "." + {"0":"3c977d2f8616250b1d4b","3":"c00ef08d6ccd41134800","4":"d978dc43548bed8136cb"}[chunkId] + ".js"; /******/ var timeout = setTimeout(onScriptComplete, 120000); /******/ script.onerror = script.onload = onScriptComplete; /******/ function onScriptComplete() { /******/ // avoid mem leaks in IE. /******/ script.onerror = script.onload = null; /******/ clearTimeout(timeout); /******/ var chunk = installedChunks[chunkId]; /******/ if(chunk !== 0) { /******/ if(chunk) { /******/ chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); /******/ } /******/ installedChunks[chunkId] = undefined; /******/ } /******/ }; /******/ head.appendChild(script); /******/ /******/ return promise; /******/ }; /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = "/"; /******/ /******/ // on error function for async loading /******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; /******/ }) /************************************************************************/ /******/ ([]);
上面就是抽離出來的webpack
運行時代碼,其實這裏,webpack
幫咱們定義了一個webpack\_require
的加載模塊的方法,而manifest
模塊數據集合就是對應代碼中的 installedModules
。每當咱們在main.js
入口文件引入一模塊,installModules
就會發生變化,當咱們頁面點擊跳轉,加載對應模塊就是經過\_\_webpack\_require\_\_
方法在installModules
中找對應模塊信息,進行加載
參考:https://www.jianshu.com/p/95752b101582
先來抽離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
含義:
chunk
公共引用才被抽取出來成爲commons chunk
module, count
) 兩個參數,返回一個布爾值,你能夠在函數內進行你規定好的邏輯來決定某個模塊是否提取成爲commons chunk
Infinity
:只有當入口文件(entry chunks
) >= 3 才生效,用來在第三方庫中分離自定義的公共模塊 要在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
做爲函數兩個參數的含義:
module
:當前chunk
及其包含的模塊count
:當前chunk
及其包含的模塊被引用的次數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
true
的時候,就表明source chunks
是經過entry chunks
(入口文件)進行code split
出來的children chunks
children
和chunks
不能同時設置,由於它們都是指定source chunks
的children
能夠用來把 entry chunk
建立的 children chunks
的共用模塊合併到自身,但這會致使初始加載時間較長* async
:即解決children:true
時合併到entry chunks
自身時初始加載時間過長的問題。async
設爲true
時,commons chunk
將不會合併到自身,而是使用一個新的異步的commons chunk
。當這個children chunk
被下載時,自動並行下載該commons chunk
修改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
的臉色怎麼來劃分:
children
爲true
,共同引用的模塊就會被打包合併到名爲children-async
的公共模塊,當你懶加載first
或者second
的時候並行加載這和children-async
公共模塊children
爲false
,共同引用的模塊就會被打包到首屏加載的app.bundle
當中,這就會致使首屏加載過長了,並且也不要用到,因此最好仍是設爲true
先來講一下哈希值的不一樣:
hash
是 build-specific
,即每次編譯都不一樣——適用於開發階段chunkhash
是 chunk-specific
,是根據每一個 chunk
的內容計算出的 hash
——適用於生產因此,在生產環境,要把文件名改爲'[name].[chunkhash]'
,最大限度的利用瀏覽器緩存。
最後,寫這篇文章,本身測試了不少demo
,固然不可能所有貼上,但仍是但願本身多動手測試如下,真的坑中帶坑。
也參考了不少文章:
https://github.com/creeperyan...
https://segmentfault.com/q/10...
https://segmentfault.com/q/10...
https://www.jianshu.com/p/2b8...