一、SplitChunksPlugin的概念
最初,Chunks块(以及在其中导入的模块)是通过内部webpack图中的父子关系连接的。CommonsChunkPlugin被用来避免它们之间的重复依赖关系,但是进一步的优化是不可能的,所以,CommonsChunkPlugin被移除,转而继续优化,所以在webpack4引出了 SplitChunksPlugin,使之可以很好地为大多数用户服务。
在默认情况下,SplitChunksPlugin 仅仅影响按需加载的代码块,因为更改初始块会影响HTML文件应包含的脚本标记以运行项目。
webpack将根据以下条件自动拆分代码块(这个只是默认条件,配置可以自己更改):
- 可以被共享的代码块或者node_mudules文件夹中的代码块。
- 体积大于30KB的代码块(在gz压缩前)。
- 当按需加载块时,并行请求(maxAsyncRequests)的最大数量将小于或等于6。
- 初始页面加载(maxInitialRequests)时并行请求的最大数量将小于或等于4。
二. SplitChunksPlugin的默认配置
下面就是默认配置(看高亮部分就是默认配置)
optimization: {
// 配置代码分割
splitChunks: {
// all代表异步,同步代码都做分割,如果式async只对异步代码做分割
chunks: 'async',
// 大于30kb才做代码分割
minSize: 30000,
// 假设maxSize:50,加入被分割代码有1m,成功打包完还会做二次分割,分割成20个50kb的文件
maxSize: 0,
// 如果文件被引入次数大于等于1次才会做代码分割
minChunks: 1,
// 同时加载的模块数为6个,如代码分割成10个库,加载时候只能加载6个
maxAsyncRequests: 6,
// 入口文件引入外库,然后做代码分割,最多支持4个
maxInitialRequests: 4,
// 组和文件的连接符号
automaticNameDelimiter: '~',
// 最大的字节数
automaticNameMaxLength: 30,
// name: true,
// 如要打包lodash和jq两个库,符合下面条件先缓存着,当所有的模块都分析好了后,
// 把符合defaultVendors组的打包到一起去,把符合default组的模块打包到一起去
cacheGroups: {
// 同步代码分割的默认配置,同时把一些库文件放到这里去
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级,比如我们打包jq时候可以打包到defaultVendors,也可以打包到default,那有了priority
// 之后,我们webpack就可以判断打包到哪个文件下,这个值越大,优先打包到这个文件中,像这里是-10>-20的。
priority: -10,
// filename: 'js/[name]/bundle.js'
},
// 如果被代码分割的模块不在module里面,然后就会做默认的打包分割
default: {
minChunks: 2,
priority: -20,
// 如果一个模块已经被打包过了,直接忽略这个模块,直接使用之前打包过的模块就可以了
reuseExistingChunk: true
}
}
}
接下来就带你对上面代码做进一步的理解:
三、配置的近一步讲解
splitChunks.chunks
有效值是all、async和initial。配置all时,你可以打包同步代码和异步代码,而async时,你只能打包异步代码。
同步代码:
// 同步代码
import _ from 'lodash'
var element = document.createElement('div');
element.innerHTML = _.join(['hansen', 'jin'], '_');
document.body.appendChild(element);
异步代码:异步代码要在模块前面加上/* webpackChunkName:"lodash"*/才可以给包的命名。
// 异步代码
function getComponent(){
// 给lodash单独加上名字
return import(/* webpackChunkName:"lodash"*/ 'lodash').then(({ default: _})=>{
var element = document.createElement('div');
element.innerHTML = _.join(['hansen', 'jin'], '_');
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
后面测试用例也是根据这两个代码进行的。
splitChunks.minSize
生成块的最小字节数,比如minSize:30000,那么30000(字节B) ÷ 1024 = 29.29kb,所以如果大于29.29kb的才做打包,如果小于则不打包,他会将多个打包文件合并在一起。如我引入了jq和lodash库,
打包后是2.13M,这种方法是推荐的,因为这样子只要请求一次就可以了。
splitChunks.maxSize
使用maxSize告诉webpack尝试将大于maxSize字节的块分割成更小的部分。该算法是确定性的,对模块的更改只会产生局部影响。就是说,你用到这个模块他才会打包。它也增加了请求数,以便更好地进行缓存。它还可以用来减小文件大小,以便更快地重建。比如:maxSize:20000,也就是20KB,加入被分割代码有1M,成功打包完还会做二次分割,分割成20个50kb的文件,不过我想,假设是一个jq文件,打包成小文件也不可能。不过一般默认配置为0。
splitChunks.minChunks
假设这个值是1,那么文件被引入次数大于等于1次才会做代码分割。
splitChunks.maxAsyncRequests
按需加载时的最大并行请求数。比如同时加载的模块数为6个,如代码分割成10个库,加载时候只能加载6个。
splitChunks.maxInitialRequests
一个入口点的最大并行请求数。
splitChunks.automaticNameDelimiter 连接符
如:看那个笑脸😄
automaticNameDelimiter: '😄',
则:
splitChunks.cacheGroups 缓存组
缓存组因该是SplitChunksPlugin中最有趣的功能了。在默认设置中,会将 node_mudules 文件夹中的模块打包进一个叫 vendors的bundle中,所有引用超过两次的模块分配到 default bundle 中。更可以通过 priority 来设置优先级。
在这个配置项里面一共有2个配置项:defaultVendors和default。然后把符合defaultVendors组的打包到一起去,把符合default组的模块打包到一起去。
我们来看看代码:
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级,比如我们打包jq时候可以打包到defaultVendors,也可以打包到default,那有了priority
// 之后,我们webpack就可以判断打包到哪个文件下,这个值越大,优先打包到这个文件中,像这里是-10>-20的。
priority: -10,
// 包被引用1次就单独拆分出来
minChunks: 1,
// 第三方模块打包之后的文件名称
filename: 'bundle.js'
},
// 如果被代码分割的模块不在module里面,然后就会做默认的打包分割
default: {
// 包被引用2次被单独拆分出来
minChunks: 2,
// 优先级
priority: -20,
// 公共模块打包出来的文件
filename: 'common.js'
// 如果一个模块已经被打包过了,直接忽略这个模块,直接使用之前打包过的模块就可以了
reuseExistingChunk: true
}
}
知识点1:
defaultVendors:只把在node_modules的文件打包到bundle.js下面,比如jq,lodash。
default: 把自己写的模块文件导入到default文件里面,并根据默认配置项配置。
知识点2:
priority: 比如一个jq模块,它可以打包以defaultVendor的形式打包,也可以用default的形式打包,但是如果我们设置了优先级,那就不一样了,优先级越高,则优先被走该条配置路线。如上面:defaultVendor的priority为-10,而default的priority为-20,那么-10的优先级级高,所以会走defaultVendor的配置。数字越大的,优先级越高。-10 > -20
知识点3:
reuseExistingChunk:如果当前块包含已经从主包中分离出来的模块,那么它将被重用,而不是生成一个新的块。这可能会影响块的结果文件名。
例子1:
// index.js
import('./a'); // dynamic import
// a.js
import 'react';
//...
将创建包含react的单独块。在导入调用中,此块a.js所包含。
原因:(4个条件)
- react来自 node_modules 文件夹
- react 体积超过30KB
- 导入调用时的并行请求数为2
- 不影响页面初始加载
react可能不会像应用程序代码那样频繁地更改。通过将它移动到一个单独的块中,这个块可以与应用程序代码分开缓存
例子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
//...
将创建一个单独的块a.js,其中包含./helpers及其所有依赖项。在导入调用时,此块与原始块并行加载。
原因:(4个条件)
helpers
是共享块helpers
大于30kb- 导入调用的并行请求数为2
- 不影响初始页面加载时的请求
将helpers的内容放入每个块将导致其代码被下载两次。通过使用单独的块,这种情况只会发生一次。