深入理解SplitChunksPlugin
前言
当谈到webpack打包优化时,SplitChunksPlugin插件是最常用也最简便的方法之一。通过简单的配置,实现代码模块的拆分,很有效的解决了代码重复、网络加载冗余内容的问题。尽管大家都配置过SplitChunksPlugin,但很少有人了解各个参数的具体含义和功能。因此,本文将详细介绍SplitChunksPlugin常用配置的具体功能,帮助你更好地了解和使用该插件。
前期准备
首先,我们需要创建一个新项目,准备这样一段代码,文件结构如下:
我们新建一个src
目录,在node_modules
文件中添加三个自定义的js,内容非常简单:导出一个字符串来标识模块内容:
// x.js
export default 'x.js';
// y.js
export default 'y.js';
// z.js
export default 'z.js';
然后,们在src
目录下直接创建 a、b、c、d、e、f、g
文件,其内容也非常简单:
// a.js
import d from './d';
import e from './e';
import x from 'x';
import z from 'z';
export default 'a' + d + e + x + z;
import(/* webpackChunkName: "async-g" */ './g');
// b.js
import d from './d'
import f from './f'
import x from 'x'
import y from 'y'
export default 'b' + d + f + x + y
// c.js
import d from './d';
import f from './f';
import x from 'x';
import z from 'z';
export default 'c' + d + f + x + z;
// d.js
export default 'd';
// e.js
export default 'e';
// f.js
export default 'f';
// g.js
import f from './f';
export default 'g' + f;
最后,我们创建一个入口文件index.js
,代码如下:
import(/* webpackChunkName: "async-a" */ './a')
import(/* webpackChunkName: "async-b" */ './b')
import(/* webpackChunkName: "async-c" */ './c')
上面就是全部的测试代码,为了更方便的看清文件之间的相互依赖关系,我做了一个关系图,可以更方便接下来的理解:
关闭SplitChunksPlugin
我们创建一个webpack.config.js,做如下配置:
const path = require('path');
const config = {
mode: 'production',
entry: {
main: './src',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
clean: true,
},
optimization: {
minimize: false,
splitChunks: false,
},
context: __dirname,
};
module.exports = config;
我们关闭了
splitChunks
,此时我们执行yarn build
打包后,查看一下打包后的结果:
此时,我们可以看到,代码分成了5个chunk
,在入口文件main.js
中,包含了一些webpack
的运行时代码用于处理其他chunk
的加载逻辑。
x模块被如何引入?
此时,我们来思考一个问题?x模块被a、b、c
三个chunk
同时应用,那么在打包后,x.js
的代码被引入了多少次呢?我们在dist文件下搜索x.js
的内容,看下结果:
很明显,x模块的内容被引入了三次,这就得思考两个问题:
x模块内容被重复的复制了3次,这看起来是一种资源的浪费,但事实上,webpack只会执行一次,只有一个实例,在运行时每次引用到该模块都返回的是同一个实例
但在文件加载时,会一一发起
HTTP
请求,假设x模块的内容非常大,此时我们加载的async-a、async-b、async-c
将变的非常大,此时加载时间会变得很长,这并不是我们想要的结果
此时,这就引出一个问题:如何解决?
使用SplitChunksPlugin
对于上面提到的问题,我们可以想象这样一种方案:当x模块被多个
chunk
引用时,如果只发出一个HTTP请求,然后将x模块添加到缓存对象中,这样在其他chunk
请求时,x模块将直接从缓存对象中查找所以,当x模块处于一个单独的
chunk
时,无论被引用多少次,都只会发起一个HTTP
请求
如何决定哪些模块需要被缓存呢,这就需要使用到cacheGroup
默认cacheGroup
SplitChunksPlugin
的配置项决定了webpack将如何创建一个新的chunk
。而创建一个chunk
,就必须要满足一个缓存组的一组规则,例如:
- 希望来自
node_modules
中的模块创建成一个chunk
;- 一个模块至少被引用3次才允许创建
chunk
类似这样的一组规则,就被称为缓存组,即:
cacheGroups
默认缓存组 当没有明确的提及缓存组的情况下,插件将使用如下默认缓存组:
cacheGroups: {
defaultVendors: {
test: /[\/]node_modules[\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
一个default以我们的程序中的任何模块作为目标,而defaultVendors是针对node_modules模块的
同时,defaultVendors的priority较大,表示具有较高的优先级,这在当一个模块存在于多个缓存组中具有重大的作用,它将决定模块属于哪个
chunk
此时,我们使用默认的cacheGroup
配置:
const path = require('path')
const config = {
mode: 'production',
entry: {
main: './src',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
clean: true,
},
optimization: {
minimize: false,
/**
* 新增 splitChunks
*/
splitChunks: {
minSize: 0
}
},
context: __dirname,
}
module.exports = config
设置minSize: 0
是因为x模块非常小,此时,如果字节数大于0,webpack就会为此创建一个新chunk
,执行一次yarn build
看下打包结果:
可以看到多了一些新的chunks
: 571.js、616.js、673.js、714.js、934.js
配合前面各个模块的依赖图,我们来解释一下为什么生成这些chunks
:
z模块 571.js
- 属于
node_modules
文件,满足defaultVendors条件- 被
a、c
两个chunks
引入,满足default条件- 但由于defaultVendors 的优先级更高,所以属于defaultVendors
y模块 616.js
属于
node_modules
文件,满足defaultVendors条件
d模块 673.js
- 不满足defaultVendors条件
- 被
a、b、c
三个chunks
引入,满足default条件: minSize: 2
f模块 714.js
- 不满足defaultVendors条件
- 被
b、c
两个chunks
引入,满足default条件: minSize: 2
x模块 934.js
- 属于
node_modules
文件,满足defaultVendors条件- 被
a、b、c
三个chunks
引入,满足default条件- 但由于defaultVendors 的优先级更高,所以属于defaultVendors
以上,是默认配置的效果。
禁用default缓存组
如果我们禁用default缓存组,那么插件将只会考虑defaultVendors缓存组,即只考虑来自node_modules
的模块,此时,webpack.config.js
:
const path = require('path')
const config = {
mode: 'production',
entry: {
main: './src',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
clean: true,
},
optimization: {
minimize: false,
splitChunks: {
minSize: 0,
cacheGroups: {
/**
* 禁用default
*/
default: false
}
}
},
context: __dirname,
}
module.exports = config
执行yarn build
,看下打包效果:
这次,我们只生成了3个
chunks
,分别对应x、y、z
三个模块。
探索SplitChunksPlugin其他功能
minChunks
前面我们已经使用过了这个属性,该属性的含义是:拆分前必须共享模块的最小 chunks 数。要想让一些新的
chunk
被SplitChunksPlugin
创建,它们必须满足一系列要求,也就是说,这个chunk
一定属于某个缓存组。
为了使这个更好理解,我们写一个希望满足以下条件的例子:
- 这个
chunks
的模块必须来自node_modules
; - 这个模块必须至少出现在3个
chunks
中
此时,webpack
应该配置成这个样子:
cacheGroups: {
default: false,
defaultVendors: {
minSize: 0,
minChunks: 3,
test: /node_modules/
}
}
执行一下yarn build
看下效果:
只有934.js
,也就是x模块被单独打包出来了,因为这是唯一一个满足上诉条件的模块。
如果我们把minChuns改为2呢?
此时,打包出了x模块和z模块。
chunks
我们经常会听到过类似于:”x模块出现在N个不同的
chunks
中“这样的话。通过chunks
属性,我们可以指定这些chunks
的类型。
首先,我们需要先介绍一下chunk的相关概念,再次回顾前面的那张关系图:
除了一个chunk可以包含多个模块之外,chunk的概念很难描述。从图中我们可以推断出有两种类型的chunk
:
async-chunk
:涉及到async-*的chunk
,它们的一个共同点是:都是由import()
函数创建的;initial chunk
:上图中只有一个chunk
是initial chunk
。什么是initial chunk
?-它和任何其他类型的chunk
一样,包含要在应用程序中呈现和使用的模块,但这种类型的chunk还包含大量所谓的运行时代码;这是将所有生成的chunk绑定在一起,以使我们的应用程序正常工作的代码;例如,运行时代码包含加载异步chunk并将它们整合到应用程序中的逻辑;通常,这种类型的chunk
可以在entry
对象中定义条目时识别出来(例如,{ entry: { main: './index.js' } })
再来看
chunks
这个参数,它接受4个参数:
async
:只包含异步chunk
initial
:只考虑initial chunk
all
:任何chunk
函数
:可以根据特定条件来过滤chunk
,只有符合条件的chunk
才会被包含进来
chunks: 'all'
值得强调的是chunks选项和minChunks之间的联系。我们使用
chunks
选项来过滤掉某些chunk
,然后SplitChunksPlugin
检查剩余chunk
的数量是否符合minChunks
的要求, 例如:
const path = require('path')
const config = {
mode: 'production',
entry: {
main: './src',
'a-initial': './src/a',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
clean: true,
},
optimization: {
minimize: false,
splitChunks: {
minSize: 0,
minChunks: 4,
chunks: 'all',
cacheGroups: {
default: false,
}
}
},
context: __dirname,
}
module.exports = config
如上的webpack
配置,我们新增了一个initial chunk:a-initial,因此,x模块会被一个或多个chunk
请求,此时我们配置了minChunks
为4且chunks
为all
,也就是说,我们希望至少被4个chunks(任何chunk)
使用,执行一下yarn build
,看下结果:
可以看到,**x模块(934.js)**被单独打包成一个chunk
。
chunks: 'async'
如果将chunks
修改为async
后,再次打包:
x模块没有被单独打包成一个chunk
,这是因为没有一个模块出现在4个异步chunks
中。
chunks: 'initial'
当我们将chunks
修改为initial
时,执行yarn build
看下结果:
我们发现并没有生成新的chunk
。这是因为这个webpack配置的含义是:
- 模块必须来自
node_modules
(使用了默认缓存组中的defaultVendors) - 一个模块至少在4个
initial chunk
中出现
在我们的项目中,只有一个initial chunk
需要这样的模块,即:a-initial
,而main.js
只通过import
函数引用模块。所以,我们需要把minChunks: 4
改为minChunks: 1
, 看下打包后的结果:
此时,
a-initial
中引用到的x模块和z模块,被打包到了一个新的chunk
中。
总结
本篇文章我们介绍了webpack中的
SplitChunksPlugin
插件最常用参数的使用方式和功能,详细解释了代码分割和优化策略的相关概念。通过本文的学习,读者可以更好地理解如何利用SplitChunksPlugin
插件优化webpack打包的性能,同时也能够更加灵活地运用该插件解决项目中的实际问题。希望本文能够为您带来实际帮助,欢迎探讨和交流。感谢阅读🙏
转载自:https://juejin.cn/post/7204404300805898297