推荐一个测试分块的工具:链接
webpack是一个模块打包工具。它会先从配置的入口开始,然后处理相关的依赖,并转换相关文件,最终输出的一些文件。为什么要研究分块呢,他能让我们更好的掌握webpack的输出规则,并且有助于提升打包速度和页面加载速度。像我之前,每次打包项目的时候6G内存都不够用,时常会在打包的过程中异常退出。后来我发现一个图表库被多个页面引用,每个页面都会打包一份,并且很多库也是一样。解决之后打包的体积小了很多,并且构建速度加快了不少。
基本概念
模块(module)、块(chunk)、资源(asset):
- 模块:一个文件即为一个模块
- 块:通常由一个及以上的模块构成
- 资源:最终输出的文件
入口文件、异步文件
入口文件:通常是配置的入口文件,在首次进入页面时会加载
异步文件:通过import函数导入的文件,只有执行到时会加载
打包原理
webpack会先从入口文件开始分析依赖。入口配置可以是字符串、数组、对象,当入口的配置是字符串或者数组时,最终也会转为对象的形式,也就是会转为以main命名块的对象形式。入口配置对象的每一个属性,都会生成对应的文件。这个文件包含了直接导入(不包含按需加载的模块)进来的所有模块文件,每个入口都含有webpack的运行时,都可以独立运行。然后每碰到按需加载的文件,都会生成对应的文件。这个文件包含所有非异步引用的模块,并且会剔除已经存在入口文件的模块。
示例:
//one.js
import A from './A'
import(/* webpackChunkName: "B" */ './B');
import(/* webpackChunkName: "C" */ './C')
//B.js
import A from './A'
import D from './D'
import E from './E'
//C.js
import A from './A'
import D from './D'
//如果关闭所有模块抽取优化,最终生成的文件如下
one.js
//包含webpack运行时代码,one文件的代码,A文件的代码
B.chunk.js
//包含B文件的代码,还有D和E文件的代码
C.chunk.js
//包含C文件的代码,还有D文件的代码
我们推荐使用webpack-bundle-analyzer来分析最终的输出文件,上面的输出如下:
抽取模块
为什么要抽取公用模块呢?我们明显看到上面的B和C文件都包含了D文件,如果这个文件比较大或者这个文件被多个文件引用,那么我们应该把这个文件单独抽个文件,这样打包后文件体积会减少,并且有助于单独缓存,减少加载一些重复的代码。
现在试想下用什么样的策略来抽取模块呢?我们看下webpack中关于分块的配置。
module.exports = {
//...
optimization: {
splitChunks: {
//选择那些块进行优化,入口块或者异步块或者全部
chunks: 'async',
//生成块的最小尺寸
minSize: 30000,
//生成块的最大尺寸。如果生成块尺寸太大,会拆分成小的块,方便浏览器缓存更小的模块
maxSize: 0,
//拆分前必须共享模块的最小数
minChunks: 1,
//异步加载最大并行请求数
maxAsyncRequests: 5,
//入口加载最大并行请求数
maxInitialRequests: 3,
//默认使用来源和名称生成抽取后的模块名称(例如vendors~main.js),此选项用来指定定界符。
automaticNameDelimiter: '~',
//拆分块的名称
name: true,
//抽取模块就是根据此来拆分的,可以继承和/或覆盖splitChunks.*中的部分选项
cacheGroups: {
default: {
//优先级
priority: -20,
//控制此缓存组选择的模块
test:/D/,
//仅在初始块时覆盖文件名
filename:'default',
//为true后会忽略minSize、minChunks、maxAsyncRequest、maxInitialRequests的规则
enforce:true,
//是否重用模块,例如编译完成后有一个异步的a.js,如果一个缓存组抽取的模块与该文件相同
//此时设置为true会放弃生成新的模块,继续保持a.js,这会影响最终的文件名称
reuseExistingChunk: true
}
}
}
}
};
webpack中首先遍历每个模块,然后用每个模块去逐个匹配缓存组(会从高优先级开始),如果能匹配到,然后判断能不能满足一些规则生成新的文件。下面是一些规则:
- 该模块是否满足chunks设置。比如他是异步加载的模块,当前的缓存组设置了只需要对入口加载的文件做优化,那他就不满足了
- 该模块是否满足minChunks设置。比如一个模块,我设置的最小共享块数是2,但它只被一个模块引用,那他就不满足
- 生成的模块是否满足minSize设置。比如我抽取的模块最终尺寸很小,那就没必要单独抽取一个模块
- 生成的模块是否满足maxInitialRequests设置。比如生成的模块属于入口加载的文件,也要控制一次不要加载过多的文件
- 生成的模块是否满足maxAsyncRequest设置。比如生成的模块属于按需加载的文件,也要控制一次不要加载过多的文件
而对于上面的实例,如果我们需要吧D模块单独抽个文件,那应该这样配置:
splitChunks:{
cacheGroups:{
"common":{
minSize:1,
minChunks:2,
test:/D/
}
}
}