1、什么是chunk、module、bundle
- module:webpack支持commonJS、es6模块化规范,module就是通过import引入的代码
- chunk:chunk包含着module,是一对多或一对一。chunk是webpack根据功能拆分出来的,分为三种情况:
- 项目入口(entry)
- 通过import()动态引入的代码
- 通过splitChunks拆分代码
- bundle: bundle是webpack打包后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出
2、splitChunks的默认配置
splitChunks就算你什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性,它的默认配置如下:
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
//entry1.js
'use strict';
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => {
let Page1 = null
import(/* webpackChunkName: "page1" */'./page1').then(comp => {
Page1 = comp
});
return (
<div>
<div>App</div>
{Page1 && <Page1/>}
</div>
)
}
document.addEventListener('DOMContentLoaded', () => {
var root = document.createElement('div');
document.body.appendChild(root);
ReactDOM.render(<App />, root);
});
//page1.js
'use strict';
import React from 'react'
import _ from 'lodash'
const Page1 = () => {
console.log($)
return (
<div>
<div>Page1</div>
</div>
)
}
export default Page1
//webpack.config.js
module.exports = {
mode: 'development',
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.scss'],
},
entry: {
entry1: './src/entry1.js',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
chunkFilename: '[name].chunk.js', //动态加载模块名称
publicPath: path.join(__dirname, 'dist/'), //动态加载模块路径
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './template.html',
filename: 'index.html',
}),
]
}

打包后结果分析:
- entry1.js就是打包之后的入口文件,webpack会把入口文件单独拆成一个chunk
- page1.chunk.js,动态加载的文件webpack会将其拆分为一个chunk
- vendors~page1.chunk.js,这个是对page1文件里面引入的第三方库进行打包,具体就是lodash那个第三方库了,这个涉及到cacheGroup稍后说明
但是我们发现有一个文件没有拆分出来,那就是entry1里面引入的第三方库react-dom,这个是为什么呢,这个就要涉及到我们接下来讲到的chunks属性的配置
3、splitChunks.chunks属性
chunks可以有三种取值:
- async表示从异步加载模块(动态加载import())中拆分
- initial表示入口模块拆分
- all表示包含以上两种
chunks的默认配置是async,也就是只从动态加载得模块里面进行拆分。上述能够把page1.js引入的第三方模块拆分出来,但是因为entry1.js属于入口chunk所以它引入的第三方库react-dom就没能拆分出。
修改chunks: all,入口模块entry.js的第三方依赖已经被成功拆分出来了

chunks: initial,page1.js的第三方依赖没有被拆分出来,入口模块entry.js的第三方依赖被拆分出来

4、splitChunks.cacheGroups属性
cacheGroups其实是splitChunks里面最核心的配置,splitChunks默认有两个缓存组:vender和default。
上述能拆分,cacheGroups里面定义了vendors这个缓存组,它的test设置为 /[\\/]node_modules[\\/]/ 表示只筛选从node_modules文件夹下引入的模块,所以所有第三方模块才会被拆分出来。除此之外还有一个default缓存组,它会将至少有两个chunk引入的模块进行拆分,它的权重小于vendors。
//entry1.js和entry2.js
import React from 'react';
import ReactDOM from 'react-dom';
import Page1 from './page1';
import $ from './lib/jquery';
const App = () => {
return (
<div>
<div>entry</div>
<Page1/>
</div>
)
}
document.addEventListener('DOMContentLoaded', () => {
var root = document.createElement('div');
document.body.appendChild(root);
ReactDOM.render(<App />, root);
});
//webpack.config.js
module.exports = {
//...
entry: {
entry1: './src/entry1.js',
entry2: './src/entry2.js',
},
optimization: {
splitChunks: {
chunks: 'all',
}
},
}

思考1:为什么entry1.js和entry2.js里面都引入了react-dom这个第三方库,它完全满足default这个cacheGroup的条件但是为什么没有被包含在entry1~entry2这个chunk中而是被纳入了vendor~entry1~entry2这个chunk里面了呢?
其实这是因为priority这个属性起了作用,它的含义是权重,如果有一个模块满足了多个缓存组的条件就会去按照权重划分,谁的权重高就优先按照谁的规则处理,default的priority是-20明显小于vendors的-10,所以会优先按照vendors这个缓存组拆分。
思考2:为什么entry1.js和entry2.js里面都引入了jquery(本地的)被打包成entry1~entry2这个chunk,而Page1没有打包?
因为Page1大小没有满足,minSize为30000的限制,而jquery满足了
5、splitChunks.maxInitialRequests属性
maxInitialRequests,它表示允许入口(entry)并行加载的最大请求数,之所以有这个配置也是为了对拆分数量进行限制,不至于拆分出太多模块导致请求数量过多而得不偿失。
注意以下几点
- 入口文件本身算一个请求
- 如果入口里面有动态加载得模块这个不算在内
- 通过runtimeChunk拆分出的runtime不算在内
- 只算js文件的请求,css不算在内
- 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
默认是值是3
//entry1.js
'use strict';
import React from 'react'
import ReactDOM from 'react-dom'
import $ from './lib/jquery';
import OrgChart from './lib/orgchart'
const App = () => {
return (
<div>
<div>entry2</div>
</div>
)
}
document.addEventListener('DOMContentLoaded', () => {
var root = document.createElement('div');
document.body.appendChild(root);
ReactDOM.render(<App />, root);
});
//entry2.js
import React from 'react';
import ReactDOM from 'react-dom';
import $ from './lib/jquery';
const App = () => {
return (
<div>
<div>entry2</div>
</div>
)
}
document.addEventListener('DOMContentLoaded', () => {
var root = document.createElement('div');
document.body.appendChild(root);
ReactDOM.render(<App />, root);
});
//entry3.js
import React from 'react'
import ReactDOM from 'react-dom'
import OrgChart from './lib/orgchart';
const App = () => {
return (
<div>
<div>App</div>
</div>
)
}
document.addEventListener('DOMContentLoaded', () => {
var root = document.createElement('div');
document.body.appendChild(root);
ReactDOM.render(<App />, root);
});
//webpack.config.js
module.exports = {
//...
entry: {
entry1: './src/entry1.js',
entry2: './src/entry2.js',
entry3: './src/entry2.js',
},
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 3,
}
},
}

我们看下entry1的并发请求数目前有哪些:
- entry1.js本身是一个对应的就是entry1.js这个文件
- vendors~entry1~entry2~entry3.chunk.js
- entry1~entry2.chunk.js
所以目前已经达到了最大的请求数3,并且jquery比orgchar大,这就是为什么不会吧orgchart.js再拆分出来的原因,那么如果我把maxInitialRequests改为4呢?


5、splitChunks.maxAsyncRequests属性
maxAsyncRequests是用来限制异步模块内部的并行最大请求数的,可以理解为是每个import()它里面的最大并行请求数量
注意以下
- import()文件本身算一个请求
- 并不算js以外的公共资源请求比如css
- 如果同时有两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
默认值是5 在上面例子中,修改entry1.js,增加page1.js
//entry1.js
'use strict';
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => {
let Page1 = null
import(/* webpackChunkName: "page1" */'./page1').then(comp => {
Page1 = comp
});
return (
<div>
<div>App</div>
{Page1 && <Page1 />}
</div>
)
}
document.addEventListener('DOMContentLoaded', () => {
var root = document.createElement('div');
document.body.appendChild(root);
ReactDOM.render(<App />, root);
});
//page1.js
'use strict';
import React from 'react'
import _ from 'lodash'
import $ from './lib/jquery';
import OrgChart from './lib/orgchart';
const Page1 = () => {
return (
<div>
<div>Page1</div>
</div>
)
}
export default Page1;
//webpack.config.js
module.exports = {
//...
entry: {
entry1: './src/entry1.js',
entry2: './src/entry2.js',
entry3: './src/entry2.js',
},
optimization: {
splitChunks: {
chunks: 'all',
maxAsyncRequests: 3,
}
},
}

- vendors~page1.chunk.js是page1里面引入的第三方库lodash,这个是根据cacheGroups进行拆分的
- page1.chunk.js是page1.js文件本身
- entry2~page1.js这个拆分的entry2和page1的共用文件jquery.js,这个是根据cacheGroups进行拆分的
那么page1这个异步模块的并发请求数正好是设置的最大值3,符合maxAsyncRequests。
这里我们发现除了jquery.js之外page1.js和entry3.js还共同引入了orgchart.js文件 ,但是却没有被拆分出来,这就是因为maxAsyncRequests的限制,如果我们把值改为4呢?

page1.js和entry3.js还共同引入了orgchart.js文件,打成了entry3~page1.js这个chunk