Awesome
1.不添加commonchunkplugin打包的文件
我们的webpack配置如下:
var webpack = require('webpack');
module.exports = {
entry: {
main:'./src/index.js',
main1:'./src/index1.js'
},
output: {
path: 'builds',
filename: "[name].entry.chunk.js",
publicPath: 'builds/',
},
plugins: [
],
module: {
loaders: [
{
test: /\.js$/,
enforce: "pre",
loader: "eslint-loader"
},
{
test: /\.js/,
loader: 'babel-loader',
include: __dirname + '/src',
},
{
test: /\.scss/,
loader: 'style-loader!css-loader!sass-loader',
// Or
// loaders: ['style', 'css', 'sass'],
//这里必须是loader后缀
},
{
test: /\.html/,
loader: 'html-loader',
}
],
}
};
此时我们在build目录下生成如下的六个文件:
此时main和main1表示的都是入口文件打包的结果,而我们的其他文件都是通过require.ensure打包而成的。
2.添加commonchunkplugin打包的文件
更新webpack如下:
var webpack = require('webpack');
module.exports = {
entry: {
main:'./src/index.js',
main1:'./src/index1.js'
},
output: {
path: 'builds',
// filename: '[chunkhash].output.js',
filename: "[name].entry.chunk.js",
publicPath: 'builds/',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// async: true,
filename:'[name].bundle.js',
children:true,
//对main和main1的文件单独打包,main的chunks集合包含了两个文件,也就是require.ensure后的两个文件
name: ['main','main1'], // Move dependencies to our main file
minChunks: 2, // How many times a dependency must come up before being extracted
}),
],
module: {
loaders: [
{
test: /\.js$/,
enforce: "pre",
loader: "eslint-loader"
},
{
test: /\.js/,
loader: 'babel-loader',
include: __dirname + '/src',
},
{
test: /\.scss/,
loader: 'style-loader!css-loader!sass-loader',
// Or
// loaders: ['style', 'css', 'sass'],
//这里必须是loader后缀
},
{
test: /\.html/,
loader: 'html-loader',
}
],
}
};
此时打包的结果为如下:
其中 name:['main','main1']的配置表示,我们的main和main1这两个chunk(entry中配置)对应的子chunk中公有的模块会被打包到一起,也就是打包到main.entry.chunk.js和main1.entry.chunk.js中,但是输出文件不再是main.entry.chunk.js和main1.entry.chunk.js,而是filename:'[name].bundle.js',也就是最后生成的main.bundle.js和main1.bundle.js。
3.当配置children后我们抽取公共模块的chunks集合
这时候在插件commonChunkPlugin中的抽取公共chunk的代码:
commonChunks.forEach(function processCommonChunk(commonChunk, idx) {
let usedChunks;
if(Array.isArray(selectedChunks)) {
usedChunks = chunks.filter(chunk => chunk !== commonChunk && selectedChunks.indexOf(chunk.name) >= 0);
} else if(selectedChunks === false || asyncOption) {
usedChunks = (commonChunk.chunks || []).filter((chunk) => {
// we can only move modules from this chunk if the "commonChunk" is the only parent
return asyncOption || chunk.parents.length === 1;
});
//(1)
var util = require('util');
console.log('------------->commonChunk',util.inspect(commonChunk, {showHidden:true,depth:4}));
//如果name=['main','main1']那么表示以入口文件开始单独打包,此时的commonChunk就是我们的main.js和main1.js
//其chunks属性表示所有require.ensure的产生的chunk
} else {
if(commonChunk.parents.length > 0) {
compilation.errors.push(new Error("CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + commonChunk.name + ")"));
return;
}
//如果found>=idx表示该chunk后续会作为一个独立的chunk来处理(独立打包),所以此处不做修改
//这里的chunks集合是包括所有entry中配置的和在name数组中配置的,如果entry中不存在这个chunk而name中存在,直接创建一个空的chunk!
usedChunks = chunks.filter((chunk) => {
const found = commonChunks.indexOf(chunk);
if(found >= idx) return false;
return chunk.hasRuntime();
});
}
let asyncChunk;
if(asyncOption) {
asyncChunk = compilation.addChunk(typeof asyncOption === "string" ? asyncOption : undefined);
asyncChunk.chunkReason = "async commons chunk";
asyncChunk.extraAsync = true;
asyncChunk.addParent(commonChunk);
commonChunk.addChunk(asyncChunk);
commonChunk = asyncChunk;
}
const reallyUsedModules = [];
if(minChunks !== Infinity) {
const commonModulesCount = [];
const commonModules = [];
usedChunks.forEach((chunk) => {
chunk.modules.forEach((module) => {
const idx = commonModules.indexOf(module);
if(idx < 0) {
commonModules.push(module);
commonModulesCount.push(1);
} else {
commonModulesCount[idx]++;
}
});
});
const _minChunks = (minChunks || Math.max(2, usedChunks.length));
commonModulesCount.forEach((count, idx) => {
const module = commonModules[idx];
if(typeof minChunks === "function") {
if(!minChunks(module, count))
return;
} else if(count < _minChunks) {
return;
}
if(module.chunkCondition && !module.chunkCondition(commonChunk))
return;
reallyUsedModules.push(module);
});
}
if(minSize) {
const size = reallyUsedModules.reduce((a, b) => {
return a + b.size();
}, 0);
if(size < minSize)
return;
}
const reallyUsedChunks = new Set();
reallyUsedModules.forEach((module) => {
usedChunks.forEach((chunk) => {
if(module.removeChunk(chunk)) {
reallyUsedChunks.add(chunk);
}
});
commonChunk.addModule(module);
module.addChunk(commonChunk);
});
if(asyncOption) {
for(const chunk of reallyUsedChunks) {
if(chunk.isInitial()) continue;
chunk.blocks.forEach((block) => {
block.chunks.unshift(commonChunk);
commonChunk.addBlock(block);
});
}
asyncChunk.origins = Array.from(reallyUsedChunks).map((chunk) => {
return chunk.origins.map((origin) => {
const newOrigin = Object.create(origin);
newOrigin.reasons = (origin.reasons || []).slice();
newOrigin.reasons.push("async commons");
return newOrigin;
});
}).reduce((arr, a) => {
arr.push.apply(arr, a);
return arr;
}, []);
} else {
usedChunks.forEach((chunk) => {
chunk.parents = [commonChunk];
chunk.entrypoints.forEach((ep) => {
ep.insertChunk(commonChunk, chunk);
});
commonChunk.addChunk(chunk);
});
}
if(filenameTemplate)
commonChunk.filenameTemplate = filenameTemplate;
});
我们看看其中的chunk.hasRuntime函数:
hasRuntime() {
if(this.entrypoints.length === 0) return false;
return this.entrypoints[0].chunks[0] === this;
}
我们看看chunk.entrypoints内部表示(见data.js下名字为main的chunk):
entrypoints:
[ Entrypoint { name: 'main', chunks: [ [Circular], [length]: 1 ] },
[length]: 1 ]
所以只有顶级chunk才会有执行环境。我们顺便看看在commonchunkplugin的处理方式:
//usedChunks是已经抽取了公共模块的chunk
usedChunks.forEach(function(chunk) {
chunk.parents = [commonChunk];
chunk.entrypoints.forEach(function(ep) {
ep.insertChunk(commonChunk, chunk);
//在每一个移除了公共代码的chunk之前插入commonChunk
});
//每一个移除了公共chunk的chunk.entrypoints添加一个chunk
commonChunk.addChunk(chunk);
});
我们顺便也给出EntryPoint的代码:
class Entrypoint {
constructor(name) {
this.name = name;
this.chunks = [];
}
unshiftChunk(chunk) {
this.chunks.unshift(chunk);
chunk.entrypoints.push(this);
}
insertChunk(chunk, before) {
const idx = this.chunks.indexOf(before);
if(idx >= 0) {
this.chunks.splice(idx, 0, chunk);
} else {
throw new Error("before chunk not found");
}
chunk.entrypoints.push(this);
}
getFiles() {
let files = [];
for(let chunkIdx = 0; chunkIdx < this.chunks.length; chunkIdx++) {
for(let fileIdx = 0; fileIdx < this.chunks[chunkIdx].files.length; fileIdx++) {
if(files.indexOf(this.chunks[chunkIdx].files[fileIdx]) === -1) {
files.push(this.chunks[chunkIdx].files[fileIdx]);
}
}
}
return files;
}
}
module.exports = Entrypoint;
我们把上面的那几句代码添加注释来看看具体的打印内容:
usedChunks.forEach(function(chunk,index) {
var util = require('util');
console.log('------------->before'+chunk.name,util.inspect(chunk.entrypoints, {showHidden:true,depth:2}));
chunk.parents = [commonChunk];
chunk.entrypoints.forEach(function(ep) {
ep.insertChunk(commonChunk, chunk);
});
var util = require('util');
console.log('------------->end'+chunk.name,util.inspect(chunk.entrypoints, {showHidden:true,depth:2}));
commonChunk.addChunk(chunk);
});
其中webpack中name的配置为 name: ["chunk",'common1','common2']。控制台打印结果可以查看这里。
所以,每一个Entrypoint.chunks表示这个Entrypoint产生了多少个chunks!chunk.entrypoints表示该chunk来自于哪些Entrypoint!从而形成一个网状结构!
3.1 commonChunkPlugin抽取之前的chunk
在(1)处,仅仅表示我们自己的main或者main1这个chunk的基本信息,包括modules等,还没有经过commonChunkPlugin进行抽取,具体内容参见data.js。
其中chunk的内部结构如下:
Chunk {
id: null,
ids: null,
debugId: 1000,
name: 'main',
//chunk对应的name
modules: [],
//该chunk来自于哪些module,main这个chunk来自于src/index.js,该module包含两个RequireEnsureDependenciesBlock
entrypoints:
[ Entrypoint { name: 'main', chunks: [ [Circular], [length]: 1 ] },
[length]: 1 ],
//入口文件为main:'./src/index.js',而entryPoint对应的chunk为对当前chunk的循环引用
chunks:[],//当前chunk的子级chunks有哪些,如require.ensure都是当前chunk的子级chunk
parents: [ [length]: 0 ],
//当前chunk的父级chunk集合,没有经过commonChunkPlugin处理main是顶级chunk
blocks: [ [length]: 0 ],
//module.blocks表示模块包含的块RequireEnsureDependenciesBlock等的个数,chunk.block表示当前chunk包含的block的个数
origins:
//当前chunk从哪些模块得到
[ { module:
NormalModule {
dependencies: [ [Object], [length]: 1 ],
blocks: [ [Object], [Object], [length]: 2 ],
variables: [ [length]: 0 ],
context: '/Users/klfang/Desktop/webpack-chunkfilename/src',
reasons: [ [length]: 0 ],
debugId: 1000,
lastId: null,
id: null,
portableId: null,
index: 0,
index2: 12,
depth: 0,
used: true,
usedExports: true,
providedExports: true,
chunks: [ [Circular], [length]: 1 ],
warnings: [ [Object], [length]: 1 ],
dependenciesWarnings: [ [length]: 0 ],
errors: [ [length]: 0 ],
dependenciesErrors: [ [length]: 0 ],
strict: true,
meta: {},
request: '/Users/klfang/Desktop/webpack-chunkfilename/node_modules/babel-loader/lib/index.js!/Users/klfang/Desktop/webpack-chunkfilename/node_modules/eslint-loader/index.js!/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
userRequest: '/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
rawRequest: './src/index.js',
parser:
Parser {
_plugins: [Object],
options: undefined,
scope: undefined,
state: undefined },
resource: '/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
loaders: [ [Object], [Object], [length]: 2 ],
//module.fileDependencies: An array of source file paths included into a module. This includes the source JavaScript file itself (ex: index.js), and all dependency asset files (stylesheets, images, etc) that it has required. Reviewing dependencies is useful for seeing what source files belong to a module.
//这个module没有引入相应的css/html/image等
fileDependencies:
[ '/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
[length]: 1 ],
contextDependencies: [ [length]: 0 ],
error: null,
_source:
OriginalSource {
_value: '\'use strict\';\n\n// var $ = require(\'jquery\');\n\n// $(\'body\').html(\'Hello\');\n\n\n// import $ from \'jquery\';\n// $(\'body\').html(\'Hello\');\n\n\n// import Button from \'./Components/Button\';\n// const button = new Button(\'google.com\');\n// button.render(\'a\');\n\n//code splitting\nif (document.querySelectorAll(\'a\').length) {\n require.ensure([], function () {\n var Button = require(\'./Components/Button\').default;\n var button = new Button(\'google.com\');\n button.render(\'a\');\n });\n}\n\nif (document.querySelectorAll(\'h1\').length) {\n require.ensure([], function () {\n var Header = require(\'./Components/Header\').default;\n new Header().render(\'h1\');\n });\n}',
_name: '/Users/klfang/Desktop/webpack-chunkfilename/node_modules/babel-loader/lib/index.js!/Users/klfang/Desktop/webpack-chunkfilename/node_modules/eslint-loader/index.js!/Users/klfang/Desktop/webpack-chunkfilename/src/index.js' },
assets: {},
built: true,
_cachedSource: null,
issuer: null,
building: undefined,
buildTimestamp: 1487137260364,
cacheable: true },
loc: undefined,
name: 'main' },
[length]: 1 ],
files: [ [length]: 0 ],
// An array of output filenames generated by the chunk.
//You may access these asset sources from the compilation.assets table.
//表示这个chunk产生的输出文件,此处为顶级chunk没有输出文件产生
_removeAndDo:{},
addChunk:{},
addParent:{},
//入口模块
entryModule:
NormalModule {
dependencies:
[ ConstDependency {},
[length]: 1 ],
blocks:
[ RequireEnsureDependenciesBlock {
dependencies: [ [Object], [Object], [Object], [length]: 3 ],
blocks: [ [length]: 0 ],
variables: [ [length]: 0 ],
chunkName: null,
chunks: [ [Object], [length]: 1 ],
module: [Circular],
loc: SourceLocation { start: [Object], end: [Object] },
expr:
Node {
type: 'CallExpression',
start: 313,
end: 488,
loc: [Object],
range: [Object],
callee: [Object],
arguments: [Object] },
range: [ 345, 486, [length]: 2 ],
chunkNameRange: null,
parent: [Circular] },
RequireEnsureDependenciesBlock {
dependencies: [ [Object], [Object], [Object], [length]: 3 ],
blocks: [ [length]: 0 ],
variables: [ [length]: 0 ],
chunkName: null,
chunks: [ [Object], [length]: 1 ],
module: [Circular],
loc: SourceLocation { start: [Object], end: [Object] },
expr:
Node {
type: 'CallExpression',
start: 543,
end: 678,
loc: [Object],
range: [Object],
callee: [Object],
arguments: [Object] },
range: [ 575, 676, [length]: 2 ],
chunkNameRange: null,
parent: [Circular] },
[length]: 2 ],
variables: [ [length]: 0 ],
context: '/Users/klfang/Desktop/webpack-chunkfilename/src',
reasons: [ [length]: 0 ],
debugId: 1000,
lastId: null,
id: null,
portableId: null,
index: 0,
index2: 12,
depth: 0,
used: true,
usedExports: true,
providedExports: true,
chunks: [ [Circular], [length]: 1 ],
warnings: [],
dependenciesWarnings: [ [length]: 0 ],
errors: [ [length]: 0 ],
dependenciesErrors: [ [length]: 0 ],
strict: true,
meta: {},
request: '/Users/klfang/Desktop/webpack-chunkfilename/node_modules/babel-loader/lib/index.js!/Users/klfang/Desktop/webpack-chunkfilename/node_modules/eslint-loader/index.js!/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
userRequest: '/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
rawRequest: './src/index.js',
parser:
Parser {
_plugins: {},
options: undefined,
scope: undefined,
state: undefined },
resource: '/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
loaders:
[ { loader: '/Users/klfang/Desktop/webpack-chunkfilename/node_modules/babel-loader/lib/index.js' },
{ loader: '/Users/klfang/Desktop/webpack-chunkfilename/node_modules/eslint-loader/index.js' },
[length]: 2 ],
fileDependencies:
[ '/Users/klfang/Desktop/webpack-chunkfilename/src/index.js',
[length]: 1 ],
contextDependencies: [ [length]: 0 ],
error: null,
_source:
OriginalSource {
_value: '\'use strict\';\n\n// var $ = require(\'jquery\');\n\n// $(\'body\').html(\'Hello\');\n\n\n// import $ from \'jquery\';\n// $(\'body\').html(\'Hello\');\n\n\n// import Button from \'./Components/Button\';\n// const button = new Button(\'google.com\');\n// button.render(\'a\');\n\n//code splitting\nif (document.querySelectorAll(\'a\').length) {\n require.ensure([], function () {\n var Button = require(\'./Components/Button\').default;\n var button = new Button(\'google.com\');\n button.render(\'a\');\n });\n}\n\nif (document.querySelectorAll(\'h1\').length) {\n require.ensure([], function () {\n var Header = require(\'./Components/Header\').default;\n new Header().render(\'h1\');\n });\n}',
_name: '/Users/klfang/Desktop/webpack-chunkfilename/node_modules/babel-loader/lib/index.js!/Users/klfang/Desktop/webpack-chunkfilename/node_modules/eslint-loader/index.js!/Users/klfang/Desktop/webpack-chunkfilename/src/index.js' },
assets: {},
built: true,
_cachedSource: null,
issuer: null,
building: undefined,
buildTimestamp: 1487137260364,
cacheable: true } }
}
3.2 commonChunkPlugin抽取公共代码抽取可视化分析
运行commonsChunkPlugin_Config中的example3代码,其中webpack的配置如下:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
main: process.cwd()+'/example3/main.js',
main1: process.cwd()+'/example3/main1.js',
common1:["jquery"],
common2:["vue"]
},
output: {
path: process.cwd()+'/dest/example3',
filename: '[name].js'
},
plugins: [
new CommonsChunkPlugin({
name: ["chunk",'common1','common2'],
minChunks:2
})
]
};
我们看看commonchunkplugin中的处理方式(else部分):
if(Array.isArray(selectedChunks)) {
usedChunks = chunks.filter(function(chunk) {
if(chunk === commonChunk) return false;
//此时commonChunk的内容是已经存在于最终的文件中了,如果它不是手动创建的chunk
//去掉下例的jquery,得到usedChunks集合
return selectedChunks.indexOf(chunk.name) >= 0;
});
} else if(selectedChunks === false || asyncOption) {
usedChunks = (commonChunk.chunks || []).filter(function(chunk) {
// we can only move modules from this chunk if the "commonChunk" is the only parent
//只是把一级子chunk的公共内容提取出来,如果有一个子chunk的父级chunk有两个那么不会被提取出来。
return asyncOption || chunk.parents.length === 1;
});
} else {
//如果当前的这个chunk有多个父级chunk,那么不会提取的
if(commonChunk.parents.length > 0) {
compilation.errors.push(new Error("CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + commonChunk.name + ")"));
return;
}
usedChunks = chunks.filter(function(chunk) {
var found = commonChunks.indexOf(chunk);
if(found >= idx) return false;
return chunk.hasRuntime();
});
}
如果你运行如下命令webpack --profile --json > stats.json并按照本文方式查看,你会发现其实可视化后各个chunk的关系是如下所示的:
这样就很容易知道commonchunkplugin的处理方式了吧。
其中有一点你要特别注意,就是如果吧webpack配置修改为如下:
new CommonsChunkPlugin({
name: ["common1",'common2','chunk'],
minChunks:2
})
此时我们继续可视化会得到如下的结果:
所以通过修改name数组中的元素的顺序往往会得到不同的chunk关系!但是需要注意一点,那就是我们的runtime执行环境会被抽取到最上级的chunk中,这里就是chunk.js:
/******/ (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 = {
/******/ 4: 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) {
/******/ if(installedChunks[chunkId] === 0)
/******/ return Promise.resolve();
/******/ // an Promise means "currently loading".
/******/ if(installedChunks[chunkId]) {
/******/ return installedChunks[chunkId][2];
/******/ }
/******/ // 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 + "" + 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;
/******/ }
/******/ };
/******/ var promise = new Promise(function(resolve, reject) {
/******/ installedChunks[chunkId] = [resolve, reject];
/******/ });
/******/ installedChunks[chunkId][2] = promise;
/******/ head.appendChild(script);
/******/ return promise;
/******/ };
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/ // 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配置为:
new webpack.optimize.CommonsChunkPlugin({
filename:'[name].bundle.js',
name: ['main','main1'], // Move dependencies to our main file
minChunks: 2, // How many times a dependency must come up before being extracted
}),
此时可视化后的结果将是如下:
注意看initial和entry,比如我们的main1,main都是initital,但是只有main1是entry类型(虽然按照上面的分析,这里的name集合和entry配置的都是完全一样,因为entry也是main和main1,但是你会发现我们的main1和main还是要通过commonchunkplugin进行一次抽取,所以导致最后的runtime部分被抽取的到main1.bundle.js中,这也是为什么我们要先加载main1.bundle.js的原因);而且,通过这个可视化结果的parents部分你也能清晰的看到每一个chunk之间的父子关系。注意:如果无法打开stats.json可以打开该文件看看是否有你自己打印的console.log内容。
4.当配置async的时候处理
4.1 首先我们修改webpack配置为不含有aync的配置:
new webpack.optimize.CommonsChunkPlugin({
name: 'main', // Move dependencies to our main file
minChunks: 2, // How many times a dependency must come up before being extracted
}),
我们可视化后会得到如下的chunk关系图:
通过上面的分析是清理之中的,最后会导致main1中的公共代码抽取到main这个chunk中,包括runtime,所以main这个chunk必须要提前加载。
4.2 我们修改webpack配置为含有aync的配置:
webpack配置为:
new webpack.optimize.CommonsChunkPlugin({
async: true,
name: 'main', // Move dependencies to our main file
minChunks: 2, // How many times a dependency must come up before being extracted
}),
首先文件的输出为:
也就是说,这种添加async配置后的chunk输出要比没有添加async多了一个文件。我们来查看一下可视化的结果:
通过上图,我们其是是添加了一个新的chunk,该chunk是CommonsChunkPlugin的name指定的这个chunk的子级chunk!
我们继续修改webpack的配置如下:
new webpack.optimize.CommonsChunkPlugin({
async: true,
name: ['main','main1'], // Move dependencies to our main file
minChunks: 2, // How many times a dependency must come up before being extracted
}),
此时你会发现可视化结果如下:
也就是对于name指定的这个数组中每一个common chunk都会添加一个公有的子级chunk!
注意:上面的两种情况下,main和main1这两个chunk都是initial,也是entry chunk,此时commonChunkPlugin不再是抽取两个chunk的公有代码,而是创建一个新的子级chunk,该子级chunk和其他的子级chunk同时加载!
4.3 我们看看commonChunkPlugin中内部的处理
首先是选择commonChunks集合:
chunkNames = options.name || options.names;
selectedChunks = options.chunks;
if(options.children) selectedChunks = false;
//如果你没有指定chunk名称,同时指定了children,那么表示抽取所有的chunk中满足使用次数的chunk
if(!chunkNames && (selectedChunks === false || asyncOption)) {
commonChunks = chunks;
}
如果你没指定commonChunkplugin中的name,也就是公共chunk的名称(上面的例子表明name的指定用于表示为那个父级chunk添加一个子级asyncChunk),那么为所有的chunks都添加这个asyncChunk。
下面是选择抽取哪些chunk,用来获取其中的公有的模块:
//用于code splitting的情况下获取commonChunk.chunks的子级chunk
else if(selectedChunks === false || asyncOption) {
usedChunks = (commonChunk.chunks || []).filter((chunk) => {
// we can only move modules from this chunk if the "commonChunk" is the only parent
return asyncOption || chunk.parents.length === 1;
});
}
如果配置了异步,那么还是对单个入口文件的子级chunk进行遍历,获取当前这个common chunk的所有的子级chunk,如main.js中所有通过require.ensure产生的子级chunk,得到的结果表示该common chunk使用的所有的usedChunks。
下面开始创建我们的asyncchunk:
let asyncChunk;
if(asyncOption) {
asyncChunk = compilation.addChunk(typeof asyncOption === "string" ? asyncOption : undefined);
asyncChunk.chunkReason = "async commons chunk";
asyncChunk.extraAsync = true;
asyncChunk.addParent(commonChunk);
commonChunk.addChunk(asyncChunk);
commonChunk = asyncChunk;
//每次循环common chunk都更新commonChunk为新创建的asyncChunk
}
每次遍历一个commonChunk都是会新创建一个全新的asyncChunk,然后这个asyncChunk作为commonChunk的子级chunk。
接着抽取usedChunks,usedChunks指的是该main.js通过require.ensure产生的所有的子级chunk集合:
if(minChunks !== Infinity) {
const commonModulesCount = [];
const commonModules = [];
usedChunks.forEach((chunk) => {
chunk.modules.forEach((module) => {
const idx = commonModules.indexOf(module);
if(idx < 0) {
commonModules.push(module);
commonModulesCount.push(1);
} else {
commonModulesCount[idx]++;
}
});
});
const _minChunks = (minChunks || Math.max(2, usedChunks.length));
commonModulesCount.forEach((count, idx) => {
const module = commonModules[idx];
if(typeof minChunks === "function") {
if(!minChunks(module, count))
return;
} else if(count < _minChunks) {
return;
}
if(module.chunkCondition && !module.chunkCondition(commonChunk))
return;
reallyUsedModules.push(module);
});
}
将usedChunk中的公共模块移除(例如require.ensure),得到的结果放在reallyUsedChunks中。同时将公共的module添加到commonChunk中。
const reallyUsedChunks = new Set();
reallyUsedModules.forEach((module) => {
usedChunks.forEach((chunk) => {
if(module.removeChunk(chunk)) {
reallyUsedChunks.add(chunk);
//该chunk已经移除了公共的module了
}
});
commonChunk.addModule(module);
module.addChunk(commonChunk);
//互相引用
});
if(asyncOption) {
for(const chunk of reallyUsedChunks) {
//抽取了公共模块得到的chunk进行遍历
if(chunk.isInitial()) continue;
//require.ensure产生的不是initial chunk
chunk.blocks.forEach((block) => {
block.chunks.unshift(commonChunk);
//block.chunks集合中存放的是我们要加载的chunk
commonChunk.addBlock(block);
//这个commonChunk也含有我们抽取掉了公共模块的哪些chunk的block
});
}
asyncChunk.origins = Array.from(reallyUsedChunks).map((chunk) => {
return chunk.origins.map((origin) => {
//更新reasons
const newOrigin = Object.create(origin);
newOrigin.reasons = (origin.reasons || []).slice();
newOrigin.reasons.push("async commons");
return newOrigin;
});
}).reduce((arr, a) => {
arr.push.apply(arr, a);
return arr;
}, []);
}
4.4 commonChunkPlugin牵涉到的其他内容
4.4.1 判断一个chunk是否是initial chunk:
isInitial() {
return this.entrypoints.length > 0;
}
因为webpack会把require.ensure内容进行单独打包,所以对于它来说是不存在我们的entrypoints的,所以其不是initial chunk,但是包含require.ensure的main却是我们的intial chunk!而且commonchunkplugin是可以产生initial chunk的,因为common chunk会被添加entrypoint:
usedChunks.forEach((chunk) => {
chunk.parents = [commonChunk];
//common chunk会变成initial chunk,因为common chunk会添加entrypoint
chunk.entrypoints.forEach((ep) => {
ep.insertChunk(commonChunk, chunk);
});
commonChunk.addChunk(chunk);
});
4.4.2 判断一个chunk是否具有runtime
hasRuntime() {
if(this.entrypoints.length === 0) return false;
//必须是initial chunk,commonchunkplugin会抽取出来entrypoint(上面例子)
return this.entrypoints[0].chunks[0] === this;
//如果main这个chunk已经被commonchunkplugin处理后,那么chunks[0]是common chunk了
}
情况1:如果我们没有加入commonchunkplugin,那么很显然我们的main和main1两个都是有runtime的,因为chunk.entrypoints表示该chunk来自于哪些入口点,而main这个chunk只有一个入口点。而entrypoint.chunks表示该入口文件产生了哪些chunks,那么很显然第一个chunk就是当前chunk本身!
情况2:如果加入commonchunkplugin,那么含有runtime的必须是最高级的common chunk。设想只有main,main1,common的情况,其中main,main1的公共代码抽取到common中,此时对于common来说,其entrypoints[0]就是main这个入口,而这个入口产生的第一个chunk就是common这个chunk(因为我们的commonchunkplugin采用的是把commonchunk插入到当前chunk之前)
usedChunks.forEach((chunk) => {
chunk.parents = [commonChunk];
chunk.entrypoints.forEach((ep) => {
ep.insertChunk(commonChunk, chunk);
//在chunks集合中common chunk在chunk之前
});
commonChunk.addChunk(chunk);
});
注意:我们的require.ensure产生的chunk不是initial chunk(不含有entrypoint),同时也是不含有runtime的!