本文的目的在于简单的介绍webpack的优化功能配置:splitChunks。
webpack5出于“开箱即用”的目的,将大部分曾经要使用插件的功能集成到了config配置中,因此用户只需要了解如何配置,即可达到优化目的,其中最常使用接触的配置是:webpack.optimization.splitChunks。
参考:
Webpack常用知识点
说到拆分,往往我们会有以下几个选择:
动态引入:import('')
、webpack.entry配置多个入口
、 splitChunks拆分抽取公共代码
相比较而言:
webpack官方文档
详解利用webpack的splitChunk拆分打包文件
splitChunks中提供了一些字段,常用的有下列属性(其他属性大多时候不需改动):
分为all(推荐)|async(默认)|initial ,区别在于打包时模块的合并策略。
最小尺寸,默认是30K,development 下是10k,设置的越大满足该尺寸的chunk 数就会变少(针对于提取公共 chunk 的时候,不管再大也不会把动态加载的模块合并到初始化模块中),当这个值很大的时候就不会做公共部分的抽取了。
如果一个模块被多个chunk所引用的次数达到了minChunks指定的次数,那么这个模块就会被打包成一个单独的chunk。但是因为我们的项目只有一个入口文件,Webpack只会生成一个chunk,这时minChunks就没有作用了,因为打包结果只有一个chunk,不需要进行代码分离。
参考:https://www.cnblogs.com/kwzm/p/10316217.html
maxInitialRequests是splitChunks里面比较难以理解的点之一,它表示允许【单个】入口并行加载的最大请求数,之所以有这个配置也是为了对拆分数量进行限制,不至于拆分出太多模块导致请求数量过多而得不偿失。
这里需要注意几点:
- 入口文件本身算一个请求
- 如果入口里面有动态加载得模块这个不算在内
- 通过runtimeChunk拆分出的runtime不算在内
- 只算js文件的请求,css不算在内
- 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
cacheGroups(缓存组)是 webpack splitChunks 最核心的配置,splitChunks的配置项都是作用于cacheGroup上的,也就是cacheGroups缓存组可以继承和覆盖来自 splitChunks.* 的任何选项。
接下来我们也是主要使用这个配置去拆分合并代码。
拆分优先级,webpack首先会遍历依赖,生成依赖树,然后对单个模块打包,最后再拆分合并。这个属性可以决定一个模块同时属于多个合并规则的时候将合并进哪个文件内。
如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再重新产生一个
当test为函数时,返回true/false,并且接收两个参数:module和chunks
module:每个模块打包的时候,都会执行test函数,并且传入模块 module 对象,module 对象包含了模块的基本信息,例如类型、路径、文件 hash 等;
chunks:是当前模块被分到哪些chunks使用,module 跟 chunks 关系可能是一对一或者多对一。
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test(module, chunks) {
//...
return module.type === 'javascript/auto';
}
}
}
}
}
};
修改完配置之后,我们可以通过webpack-analyze
看到打包出来的具体效果,衡量自己的优化程度。
在next中,就是如下配置:
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withTM(withBundleAnalyzer(withImages(withAntdLess(nextConfig))));
npm run analyzer
一般而言,没有改动过的Next自带的配置如下:(从next.config中打印出来的数据)
{
emitOnErrors: true,
checkWasmTypes: false,
nodeEnv: false,
splitChunks: {
chunks: [Function: chunks],
cacheGroups: { {
framework: {
chunks: 'all',
name: 'framework',
test: [Function: test],
priority: 40,
enforce: true
},
lib: {
test: [Function: test],
name: [Function: name],
priority: 30,
minChunks: 1,
reuseExistingChunk: true
}
},
maxInitialRequests: 25,
minSize: 20000
},
runtimeChunk: { name: 'webpack' },
minimize: true,
minimizer: [ [Function (anonymous)], [Function (anonymous)] ]
}
想要增加分包策略,只需要像如下代码,在next暴露出的config中配置
webpack(config, { webpack, isServer }) {
try {
if (!isServer) {
const cacheGroups = config.optimization.splitChunks.cacheGroups;
console.log(cacheGroups)
}
} catch (e) {
console.error('webpack cacheGroups error: ',e);
}
return config;
}
我的主要目的在于优化项目的首包,也就是app.js,所以主要查看的也是这个包。
首先打印出项目的打包文件如图:(假装有图)
分析也许是受限于:maxInitialRequests,或者其他的原因,react / mobx等包都没有拆分出app的包,须知,这种固定依赖往往可以放在CDN中很久不变动,要是每次都跟着app包重新下载,不利于CDN利用和首屏渲染。
故第一份优化配置如下:
拆分公共依赖库:antd / mobx / react / moment / lodash等等,当然后续还可以拆分其他的模块。
const cacheGroups = config.optimization.splitChunks.cacheGroups;
// 1.mobx
cacheGroups.antd = {
name: 'antd',
test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
enforce: true,
chunks: 'all',
};
// 2.工具类
cacheGroups.vendors = {
name: 'vendors',
test: /[\\/]node_modules[\\/](mobx|axios|lodash|moment)[\\/]/,
enforce: true,
chunks: 'all',
priority: 20,
};
// 3.utils&config
cacheGroups.utils = {
name: 'utils',
test: /[\\/]src[\\/]utils[\\/]/,
enforce: true,
chunks: 'all',
priority: 20,
};
拆分后,可见打出来三个新的包antd 、vendors、utils,项目的整体大小少了0.6M,如果觉得antd的包过大,也可以再使用maxSize限制,或者细化test正则。
接下来再分析app的页面,看到某个模块下引入的配置文件基本上占用了一半的大小,这个配置文件是在一个大的activity文件夹下的多个小活动中的config.js。这是因为之前为了在服务器端渲染的时候能够获取到配置数据,所以在_app.js引入造成的问题,随着后续的需求展开,这里的影响可能会越来越大,因此必须得想个办法拆分出去。
想要把这一部分的包拆分出去,一开始考虑了几个方法:
一开始写了一个类似下方这样的动态引入代码,但是在mobx数据初始化时才会进行下载,无法获取到配置数据。
await ConfigRes.then((Config) => {
const ConfigResModule = ConfigRes.default;
});
经过尝试最后把test的名称正则改成只识别配置文件夹下的config.js文件。
优先级不能填太高,优先级填的高的话,activity下方所有活动的文件都会被打包进来,有使用和没有使用的文件都放在了一起,这样反而无法达到减负的目的。因此优先级填最低,这样可以使“未被划分进其他包、最后只能留在app.js包”中的这一些activity数据抽取出来。
再做一层保险设置maxSize,以免以后配置了太多活动导致这个包过大。
cacheGroups.activity = {
name: 'activity',
test: /[\\/]src[\\/]activity[\\/].*[\\/]config.js/,
enforce: true,
chunks: 'all',
priority: -30,
maxSize:200000,
};
方法二其实比较繁琐,但是这样做可以使开发可以不去关注打包信息,只关注自己要开发的代码,而不用把代码挪过来挪过去。
方法二已实现后,暂时没有考虑。
需要做性能测试。
可能要经过压测和稳定性测试,以及性能测试看看是不是真的有效。