代码分割是一个概念,和webpack无关,在没有webpack的时候,代码分割这个概念也存在。通过合理的代码分割会让我们程序运行的性能更高,在没有webpack的时候,我们需要思考手动做代码分割怎么样合适。但是在有webpack后,我们只需在webpack配置中使用几个配置项,webpack就会知道我们的代码怎么样做代码分割合适,就会自动做代码分割,不在需要我们去考虑这个事情。
1.手动代码分割演示
安装loadsh
src/index.js
import _ from "loadsh";
let str = _.join(['hello','world'],'-');
console.log(str)
运行webpack
webpack打包输出的信息,main.js文件是1.38M。main.js文件之所以这么大,是因为将整个loadsh库全部打包进main.js文件中。当我们的业务代码非常长,引用的库文件也比较多时,main文件会非常大,这会导致用户体验非常差。接下来手动拆分一下
文件结构
myProject
|-dist
|-node_modules
|-src
|-util
|-math.js
+ |-loadsh.js
|-assets
|-css
|-index.css
|-less
|-index.less
|-sass
|-index.scss
|-images
|-wali_logo.png
|-index.html
|-index.js
|-package.json
|-webpack.config.js
|-postcss.config.js
|-.babelrc
src/util/loadsh.js
import _ from "loadsh"
window._ = _;
src/index.js
- import _ from "loadsh";
let str = _.join(['hello','world'],'-');
console.log(str)
webpack.config.js
module.exports = {
...
entry:{
+ loadsh:'./src/util/loadsh.js'
main:'./src/index.js'
},
...
}
运行webpack
webpack打包看到main.js文件29.1KB,并没有将loadsh打包进main.js文件,而是单独打包成一个文件。我们测试下打包后能不能运行,打开dist/index.html
,打开chrom控制台,看到控制台输出hello-world
。其实webpack可以自动帮我们进行代码分割,不需要我们从架构上区分,减轻了开发者的工作。
注意:
不知道大家在写entry时,有没有将loadsh
和main
顺序写反,小菜第一次就他们顺序写反了。导致打完包后,浏览器包了一个_ is not defined
。看index.html源码中也将loadsh
库加载进来了。
为什么会出现这个问题?
是因为在index.html中先加载main.js
然后在加载loadsh
但是在执行顺序上main.js文件需要依赖loadsh库文件。虽然浏览器是并行加载js文件的。但是在有依赖关系js代码中,我们并不能保证loadsh一定加载在main文件前。
2.webpack配置代码分割
在配置webpack之前,我们将上面的代码进行回滚
回滚webpack.config.js
module.exports = {
...
entry:{
- loadsh:'./src/util/loadsh.js'
main:'./src/index.js'
},
...
}
回滚文件
myProject
|-dist
|-node_modules
|-src
|-util
|-math.js
- |-loadsh.js
|-assets
|-css
|-index.css
|-less
|-index.less
|-sass
|-index.scss
|-images
|-wali_logo.png
|-index.html
|-index.js
|-package.json
|-webpack.config.js
|-postcss.config.js
|-.babelrc
代码回滚完成后,我们在webpack参数中配置下,让webpack帮助我们做代码分割。
修改src/index.js
import _ from "loadsh";
let str = _.join(['hello','world'],'-');
console.log(str)
webpack.config.js
module.exports = {
...
optimization:{
usedExports: true,
+ splitChunks:{
+ chunks:'all'
+ }
},
...
}
从小菜截图中可以看到,webpack将公共的类库提取出来,打包成一个文件。
3.异步代码分割
在上面我们已经将loadsh进行了代码分割,不过在src/index.js
中的代码是同步的,那webpack能不能将我们的代码做异步分割呢?
src/index.js
function getComponent(){
return import('loadsh').then(({ default:_ }) =>{
let element = document.createElement('div');
element.innerHTML = _.join(['hello','world'],'**');
return element;
})
}
getComponent().then(ele=>{
document.body.appendChild(ele);
})
安装
yarn add @babel/plugin-syntax-dynamic-import
修改.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "67"
},
"useBuiltIns": "usage"
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
+ "@babel/plugin-syntax-dynamic-import"
]
}
然后在运行webpack
发现webpack将异步引入的库文件也打包成了一个文件。
4.代码分割修改文件名
上面webpack将loadsh库文件做代码分割后,文件名称变成了0.js,不在是我们熟悉的loadsh.js如果想要修改文件名称,那就跟小菜继续往下走。
scr/index.js
function getComponent(){
+ return import(/*webpackChunkName:"loadsh"*/ 'loadsh').then(({ default:_ }) =>{
let element = document.createElement('div');
element.innerHTML = _.join(['hello','world'],'**');
return element;
})
}
getComponent().then(ele=>{
document.body.appendChild(ele);
})
运行webpack
5.详解SpiltChunksPlugin参数
SpiltChunksPlugin默认参数
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
参数 | 默认 | 说明 | 值 |
---|
chunks | async | 设置代码分割类型,和cacheGroups 配置配合使用 | async 对异步代码分割
all 对同步和异步代码分割 |
minSize | 30000(30kb) | 当引入的模块大于30kb才会做代码分割 | |
maxSize | 0 | 当引入的模块大于maxSize 时,会尝试对引入的模块进行二次拆分,一般不用配置 | |
minChunks | 1 | 当一个模块被至少引入1次,才会做代码分割 | |
maxAsyncRequests | 5 | 当引入模块10个时,只会将前5个模块进行打包 | |
maxInitialRequests | 3 | 入口文件引入的模块如果超过3个,只会将前3个模块做代码分割 | |
automaticNameDelimiter | ~ | 文件连接符 | |
name | true | 拆分块的名称,让cacheGroups 里面的名字有效 | |
cacheGroups | {} | 对符合代码拆分的模块进行一个分类 | |
cacheGroups参数
参数 | 类型 | 说明 | 值 | |
---|
priority | number | 有限权,当一个模块都符合cacheGroups分组条件,将按照优先权进行分组,priority值越大,优先权越高 | -10 | |
filename | String|Function | 拆分的名称,一般不设置,默认生成vendors~mian 。vendors 分组名称,~ 连接符,main 引入模块的入口文件 | | |
reuseExistingChunk | boolean | 如果当前块包含已从主束拆分的模块,则将重用它而不是生成新的块。比如 import a from ‘A’ import b from ‘B’在打包时候,按照打包顺序也会将b 打包进a 模块,但是在a 打包之前,如果已经将b 模块进行过打包,那么就不会将b 模块在打包到a 模块中 | | |
test | function (module, chunk) | RegExp | string | 控制此缓存组选择的模块。test:/[\\/]node_modules[\\/]/ 必须要在node_modules模块在才可以 | | |
enforce | boolean | 将对 splitChunks.minSize splitChunks.minChunks splitChunks.maxAsyncRequests splitChunks.maxInitialRequests 配置忽略 | | |
webpack.config.js
module.exports = {
...
optimization: {
+ splitChunks: {
+ chunks: 'all',
+ minSize: 30000,
+ maxSize: 0,
+ minChunks: 1,
+ maxAsyncRequests: 5,
+ maxInitialRequests: 3,
+ automaticNameDelimiter: '~',
+ name: true,
+ cacheGroups: {
+ vendors: {
+ test: /[\\/]node_modules[\\/]/,
+ priority: -10
+ },
+ default: {
+ minChunks: 2,
+ priority: -20,
+ reuseExistingChunk: true
+ }
+ }
+ }
}
};
运行webpack
6.示例1说明
// index.js
import('./a'); // dynamic import
// a.js
import 'react';
//...
结果:创建包含react的单独块,在导入调用时,react块和./a的原始块并行加载
原因:
- react块来自node_modules的模块
- react大于30kb
- 导入调用时的并行请求数为2
- 不影响初始页面加载时的请求
7.示例2说明
// entry.js
// dynamic imports
import('./a');
import('./b');
// a.js
import './helpers'; // helpers is 40kb in size
//...
// b.js
import './helpers';
import './more-helpers'; // more-helpers is also 40kb in size
//...
结果:将创建一个./helpers单独的块及其所有依赖项。在导入调用时,此块与原始块并行加载
原因:
./helpers
块在两个导入调用之间共享helpers
大于30kb- 导入调用的并行请求数为2
- 不影响初始页面加载时的请求