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...