谈一谈Webpack 的SplitChunks
webpack官网中,有这么一句话,我不是很认同:
开箱即用的
SplitChunksPlugin
对于大部分用户来说非常友好。
可以说,性能优化最重要的部分就是懂得如何分包了。因此,就来谈谈webpack中的分包。
首先我们需要了解一个概念。
什么是chunks
一个常考的面试题是 module、chunk、bundle是什么。对于初学者,这个是很迷惑的。我们首先要知道webpack一个简单的理解:
Webpack: 构建你的assets。左侧的资源通过webpack后,都能输出web浏览器能识别的资源! 归因于node构建了一个世界,让这个世界所有非js资源,都在js世界下的规则下处理:
css: 本质是style下的一个字符串,webpack处理css文本后,如果要直接作为style标签插入,再使用style-loader,如果要拿出来,则通过MiniCssExtractPlugin插件进行处理。 less: 本质最终要转化成css,因此通过less-loader,转化为css,再重复上面的操作。 typescript: 浏览器是没法直接识别的,通过ts-loader。来转化为浏览器能识别的js。
以上这些,都是为了做一件事:
以上图webpack为划分点,我们就可以区分module和bundle。即:左边是资源就是moudle,右边的资源就是bundle。
一个ts文件、图片、less、pug等都是一个module,而打包后的产物,总的称呼就是bundle。
那么chunk是什么呢?
对于打包产物bundle, 有些情况下,我们觉得太大了。 为了优化性能,比如快速打开首屏,利用缓存等,我们需要对bundle进行以下拆分,对于拆分出来的东西,我们叫它chunk。
我们试着打包一下!
默认配置
现在我们创建src/index.js 和 src/a.js
index.js | a.js |
---|---|
import lodash from "lodash";,import { a } from "./a";,console.log(lodash, a); | export const a = "i am aaaaaa";,console.log(a);, |
目录结构:
webpack.config.js 配置和打包结果为:
webpack.config.js | |
---|---|
{, mode: "production",, entry: {, main: "./src/index.js",, },, output: {, path: path.resolve(__dirname, "dist"),, filename: "[name].js", , clean: true,, },,} | ![]() |
默认的,webpack没有进行分包,全部都打在一起了。
一些配置字段
Webpack 的分包主要在optimization.splitChunks属性,现在我们来尝试使用不同的配置字段来看看效果。
optimization.splitChunks.chunks
Chunks 有三个提供的值,分别是 async、initial、all
async
此值是默认的chunks值,也就是说,我们的第一次打包实际上就是实行了async,该值的意思是:对于动态加载的模块,默认配置会将该模块单独打包。使用以下语法进行动态加载(还有其他写法):
import('lodash')
修改index.js,然后运行build命令,为了文件更直观,我们将optimization.chunkIds的值设置为named
index.js | |
---|---|
import { a } from "./a";,import('lodash').then(lodash => {, const res = lodash.default.add(3,4), console.log(a, res);,}) | ![]() |
可以看到lodash被分出一个单独的包了。
以下是webpack对于默认配置的说法:
webpack 将根据以下条件自动拆分 chunks: 新的 chunk 可以被共享,或者模块来自于
node_modules
文件夹 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积) 当按需加载 chunks 时,并行请求的最大数量小于或等于 30 当加载初始化页面时,并发请求的最大数量小于或等于 30 当尝试满足最后两个条件时,最好使用较大的 chunks。
进行实验后,发现并不准确,比如两个入口引入lodash,lodash并未被抽出来
index.js | other.js |
---|---|
import lodash from "lodash";,import { a } from "./a";,console.log(lodash, a); | import lodash from "lodash";,,console.log("lodash", lodash); |
配置与打包
配置 | 打包 |
---|---|
{, entry: {, main: "./src/index.js",, other: "./src/other.js",, },, optimization: {, chunkIds: "named",, },,} | ![]() |
默认配置只会抽出动态加载的模块,通常情况下,不是立即需要的包,可以考虑动态加载,比如导出excel的包,echarts,monaco-editor等。 | |
#### initial |
当chunk为initial或者为alll时,webpack打包遵循以下配置(取名default):
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'initial',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
尽管你的配置可能是这样:
{, entry: {, main: "./src/index.js",, other: "./src/other.js",, },, optimization: {, chunkIds: "named",, splitChunks: {, chunks: "initial",, },, },,} | ![]() |
---|
只有当chunks 不为async时,webpack打包的默认配置才会是default配置
新的 chunk 可以被共享,或者模块来自于
node_modules
文件夹 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积) 当按需加载 chunks 时,并行请求的最大数量小于或等于 30 当加载初始化页面时,并发请求的最大数量小于或等于 30
all
当chunks值为all时,基本跟initial值相同,我们来实验一下它的不同点;
我们将在other.js对lodash动态引用:
import('lodash').then(lodash => {
const res = lodash.default.add(3,4)
console.log(res);
})
然后分别使用chunks为all 和initial的值,看看效果:
all | initial |
---|---|
![]() | ![]() |
可以看到,对于两个入口文件引用lodash, 如果一个是正常引入,一个是动态引入,initial会打包成两份,而all的话,只会有一份,因此,通常情况下,all的优于initial的。
optimization.splitChunks的其他默认配置
{
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
}
minSize:此配置是指,将要被分包的chunks,如果压缩前体积不足20k,将不会被拆包。 minChunks:某个chunks被多次引用,如果这个引用次数小于某个值,将不会被拆包。 ... 以上条件满足一个将会被分包。 enforceSizeThreshold:如果某个chunks的大小超过了50k,以上限制将不会生效。
optimization.splitChunks. cacheGroups
cacheGroups有两个默认缓存策略,也就是chunks为all和initail时的默认配置:
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
}
defaultVendors 会将源代码中所有引入node_modules的文件打包成为一个大的chunks。 default 则是对于多入口引入的相同模块超过两次后,进行拆包操作,需要注意的是,我们通常操作的单页面应用,默认只有一个入口文件,如果有如下代码:
// index.js
import lodash from "lodash";
import { a } from "./a";
import { b } from "./b";
console.log(lodash, a, b);
// a.js
export const a = "i am aaaaaa";
console.log(a);
import "./c";
// b.js
export const b = "i am bbbbbbbbb";
console.log(b);
import "./c";
// c.js
console.log("ccccccccc");
a.js 和 b.js 共同引用了c.js。此时a、b、c都从属于index.js入口,虽然c.js被引用了两次,但c.js并不会分成单独的包,如果要将c.js单独打包,考虑动态加载。
通常,我们只需要默认的配置即可满足大部分需求,有的时候我们可能想单独抽出react相关代码,那么这需要以下配置。
react: {
name: "ReactAbout",
test: /react/,
priority: 1,
},
打包效果
// other.js
import('lodash').then(lodash => {
const res = lodash.default.add(3,4)
console.log(res);
})
import('./style/a.css')
import('./style/b.css')
import('./style/c.css')
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
}
css
css也是性能优化的一部分,有一种方式是通过style- loader将css以style标签的形式插入到文档,这种方式正常引用无法进行分包。但可以通过动态引入的方式分包。
另外有一个MiniCssExtractPlugin插件进行css的分包。此插件默认为每个入口单独抽出css,也可以进行cacheGroups的配置,满足条件时,会将多个入口的css打包在一起。
css: {
name: "css",
test: /\.css$/,
minChunks: 1,
enforce: true,
}
转载自:https://juejin.cn/post/6992887038093557796