自Webpack4之后,代码拆分的插件由CommonsChunkPlugin变成了SplitChunksPlugin,而且没必要单独引用,集成到了Webpack之中,经过配置下的optimization.splitChunks和optimization.runtimeChunk就能够控制了。vue
前两天研究了一下CommonsChunkPlugin插件,总结出来一条经验,就是要理解这个插件,单纯看如何配置它,是不会懂它的。node
先知道它的设计思路,再学习如何配置它。react
Webpack4的代码拆分方案,彻底换了一个插件,想必设计思路和使用上差异会比较大,实际上也的确如此。webpack
若是是一些简单的重复代码的拆分,CommonsChunkPlugin是能够胜任的。但一些复杂的场景,CommonsChunkPlugin就不行了。web
咱们的项目结构是这样的:浏览器
这时候用Webpack打包此项目,使用CommonsChunkPlugin的话,会将React.js Vue.js这些库打包到vendor.js中。缓存
这样作的问题:bash
不是将React.js、Vue.js打包到同一个vendor Chunk中,而是Webpack经过分析,将React.js打包到一个vendor~Greeter1~Greeter2.js中,将Vue.js打包到一个vendor~Greeter3.js中,这样分别打包公共代码。并发
而后首屏加载的时候,只加载入口Chunk index.js。等用户查看Greeter1.js的时候,再并行加载Chunk Greeter1.js和Chunk vendor~Greeter1~Greeter2.js。查看Greeter3.js的时候,再并行加载Chunk Greeter3.js和Chunk vendor~Greeter3.js。app
这样,解决了上面提到的两个问题,首屏速度和流量浪费。
SplitChunksPlugin就是能够应付上面描述的复杂的拆分状况,比较理想的拆分代码。
按照上面描述的,咱们新建文件,目录结构以下:
文件内容以下:
// index.js
// 这样就是异步加载Greeter一、二、3 三个Chunk
import(/* webpackChunkName: "Greeter1" */'./Greeter1').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
import(/* webpackChunkName: "Greeter2" */'./Greeter2').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
import(/* webpackChunkName: "Greeter3" */'./Greeter3').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
复制代码
// Greeter1.js / Greeter2.js
// 咱们就这样模拟引用了React。Greeter2.js和Greeter1.js长得同样。
import React from 'react';
console.log(React);
export const greeter = function () {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
复制代码
// Greeter3.js
// Greeter3中引用的是Vue
import Vue from 'vue';
console.log(Vue);
export const greeter = function () {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
复制代码
再来看看关键的Webpack配置
module.exports = {
entry: {
index: __dirname + "/app/index.js",
},
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "[name].js", //打包后输出文件的文件名
chunkFilename: '[name].js',
},
mode: 'development',
devtool: false,
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
},
}
}
},
}
复制代码
关键的就是optimization下的配置,咱们先只关注cacheGroups下的vendors配置。其中的test是分割代码的规则,表明node_modules文件夹下的代码都要被抽离出来。咱们运行一下Webpack,看看输出结果:
我查看了一下vendor~Greeter1~Greeter2.js文件,里面是React打包后的代码。 我查看了一下vendor~Greeter3.js文件,里面是Vue库打包后的代码。
再利用打包分析,看到:
这不就是咱们设想的那种理想状况。若是是CommonsChunkPlugin,配置后,只会帮咱们打包出一个vendor.js的公共Chunk,而SplitChunksPlugin,咱们只是告诉它node_modules下的文件要抽离出来,Webpack就根据项目的引用状况,自动分理处两个公共Chunk vendor~Greeter1~Greeter2.js 和 vendor~Greeter3.js
解决复杂场景下的代码拆分问题。针对异步加载中公共模块的拆分,咱们只需设置须要被公共打包的代码,SplitChunksPlugin就会自动帮咱们按照各异步模块的需求,将公共的Chunk拆分红一些小的公共Chunks。供各异步模块使用。而且这些公共Chunks不会首屏加载,会随着使用使用它们的异步模块,使用时再一同并行加载。
核心思路:根据咱们给的规则拆分代码,而后针对拆分的公共Chunk,再次拆分。
到这里,还需一种极端状况,就是被拆分出来的公共Chunk,太多了。Webpack的初衷是合并代码啊,这又给拆碎了。
过多Chunk致使的问题就是浏览器同时须要并发请求太多的js。
一样的SplitChunksPlugin也替咱们想到了。
咱们再来作一个实验。
我画一个图说明状况:
是否跟你预想的同样,出现了三个公共Chunk,也就是咱们上图画的公共部分,分别包含了header1~3的代码。再看更直观的效果图:
浏览器加载Greeter4.js的时候,须要同时加载default~Greeter1~Greeter4.js、default~Greeter2~Greeter4.js、default~Greeter3~Greeter4.js三个Chunk。也就是用户看Greeter4.js时,需并行请求4个js文件。
问题就在于咱们把公共包拆的过于细,有可能会出现,加载一个异步Chunk的时候,须要同时而且请求不少的公共Chunk,这不是咱们想看到的,为此,SplitChunksPlugin提供给咱们一个属性maxAsyncRequests,限制最大并行请求数。
目前的最大的并行请求数是加载Greeter4.js时的4,咱们设置成3,看看什么效果:
optimization: {
splitChunks: {
maxAsyncRequests: 3, // 在此设置
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
复制代码
运行webpack,效果以下:
Greeter4将helper3.js打到一个Chunk里,而后helper一、helper2单独打包,这样Greeter4的并行请求数等于3,符合预期。
一样的将helper3.js一样被Greeter3.js引用,因此也打包到了Greeter3中,形成了重复打包helper3。为了减小而且请求数,就会致使必定程度的重复打包,咱们要作的,就是经过配置在平衡而且请求数和重复打包率上作一个平衡。
总的来讲SplitChunksPlugin仍是很智能啊,咱们只是提出要求(并行请求数要小于等于3),它就会基于此条件为咱们的进行拆包和组合包。
即便咱们不写optimization,Webpack也会帮助咱们进行代码拆分,至关于咱们写了以下的配置:
splitChunks: {
chunks: "async", // 默认只处理异步chunk的配置
minSize: 30000, // 若是模块的最小体积小于30,就不拆分它
minChunks: 1, // 模块的最小被引用次数
maxAsyncRequests: 5, // 异步加载Chunk时的最大并行请求数
maxInitialRequests: 3, // 入口Chunk的最大并行请求数
automaticNameDelimiter: '~', // 文件名的链接符
name: true, // 此处写成false,公共块就不会是default~Greeter1~Greeter4.js了,而是0.js这样命名Chunk。
cacheGroups: { // 缓存组,拆分Chunk的规则
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10, // 此数越大,越优先匹配
},
default: {
minChunks: 2, // CommonsChunkPlugin的minChunks既能够传方法,也能够传数字,如今只能够传数字了,若是你想传方法,用test属性
priority: -20,
reuseExistingChunk: true // 配置项容许重用已经存在的代码块而不是建立一个新的代码块。这句我不懂,有知道的小伙伴麻烦告诉我一下
}
}
}
复制代码
能够看到默认配置只对异步加载的Chunk有效,缘由是配置了 chunks: "async"。
如下是默认配置的描述:
这些描述分别对应了上面哪条配置,相信你们都清楚了。若是没有通过分析,这些描述真是让人摸不着头脑。
maxInitialRequests字段咱们尚未解释,看字段名字应该是初始化时,也就是针对入口Chunk的分割吧,因而我作了以下配置:
module.exports = {
entry: {
Greeter1: __dirname + "/app/Greeter1.js",
Greeter2: __dirname + "/app/Greeter2.js",
Greeter3: __dirname + "/app/Greeter3.js",
Greeter4: __dirname + "/app/Greeter4.js",
},
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "[name].js", //打包后输出文件的文件名
chunkFilename: '[name].js',
},
mode: 'development',
devtool: false,
optimization: {
splitChunks: {
chunks: "initial", // 默认只处理异步chunk的配置
maxInitialRequests: 3, // 一个入口最大并行请求数
cacheGroups: { // 缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
}
复制代码
咱们来看打包效果:
我将maxInitialRequests调成5,再来看打包效果:
打包的结果,和咱们分析异步Chunk的提取策略一致,限制为5的时候,即便是Greeter4.js的最大而且请求数才是4,因此能够尽情的拆包。但限制为3的时候,Webpack就不把helper3.js单独拆成一个公共Chunk了,而是分别打包到引用了它的Greeter4.js和Greeter3.js里,以此来限制Greeter4这个入口Chunk被加载时,并行请求为3。能够说maxInitialRequests就是 针对多入口限制拆包数量的maxAsyncRequests。
说了这么多,尚未提到拆分runtime呢。SplitChunksPlugin拆分runtime只需配置一个属性,以下:
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: "initial", // 默认只处理异步chunk的配置
minSize: 30000, // 若是模块的最小体积小于30,就不拆分它
minChunks: 1, // 模块的最小被引用次数
maxAsyncRequests: 5, // 按需加载的最大并行请求数
maxInitialRequests: 5, // 一个入口最大并行请求数
automaticNameDelimiter: '~', // 文件名的链接符
name: true,
cacheGroups: { // 缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
复制代码
我这里仍是沿用上一个例子,打包结果以下:
颇有意思,针对咱们四个入口文件,分别生成了四个文件,runtime~Greeter1到4。这也符合预期,使用哪一个入口的代码,就也加载它对应的runtime文件。
如今,咱们仍是回到最初的一个简单的例子,结束咱们今天的研究。
不考虑异步加载模块,只是分离业务代码,第三方库代码和runtime代码。
入口文件index.js,里面只引用了react。 配置以下:
module.exports = {
entry: {
index: __dirname + "/app/index.js",
},
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "[name].js", //打包后输出文件的文件名
chunkFilename: '[name].js',
},
mode: 'development',
devtool: false,
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: "initial",
automaticNameDelimiter: '~',
name: true,
cacheGroups: { // 缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
},
}
}
},
}
复制代码
咱们看到react是第三方库,提取到了vendors~index.js中,runtime代码,提取到了runtime-index.js,业务代码,就是index.js。
Webpack的官方文档,没有解释的那么清楚,对于Webpack的学习,须要多多动手,在实践中,帮助咱们学习体会Webpack。
SplitChunksPlugin要理解起来仍是稍微复杂一点的,它的设计就是为了搞定复杂的拆分状况。但摸清它的原理后,发现它仍是很强大的,经过几项配置,就能够完成复杂状况下的代码拆分。