webpack性能优化的方式
webpack性能优化主要有两个方向:
1.打包之后的结果
2.打包之后的下载速度
接下来,我来阐述一下这两个方向的优化途径以及实现~
打包之后的结果
一、代码分离
使用webpack将代码打包之后的结果最后都会放在一个主包里,主包里面不仅有我们自己编写的代码也有第三方库代码,十分不利于管理,而且webpack为了支持运行时模块化,运行时的runtime代码也会打包到主包下面,这样导致了主包十分庞大,默认情况下浏览器会全部加载这些文件,这样会延长加载的时间,首屏渲染速度降低。
runtime主要是指:在浏览器运行过程中,webpack 用来连接模块化应用程序所需的所有代码。它包含:在模块交互时,连接模块所需的加载和解析逻辑。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。
分包将主包分成一个个小包,用户在第一次加载时才会加载对应的包,其他的包在使用到的时候才会进行加载,或者在浏览器有闲置时间的时候下载。
1.分离入口起点
可以将不同代码逻辑分离在不同的文件中,通过配置不同入口文件打包时加载不同文件,这样就不会一下子下载所有代码
entry配置变为对象,对象里面使用键值对的形式来表明包的名称以及路径,同时在出口使用占位符来配置打包之后的名字,属于手动分包
entry: { //这里将入口文件分为两个
main:"./src/main.js",
index:import:"./src/index.js",
},
使用多入口加载入口文件的时候,在输出配置项也要进行加载,因为多入口进行打包,那么也要多入口输出,否则会引起报错
output: {
clean: true,
filename: "[name].js", //name为占位符
path: path.resolve(__dirname, "./bundle"),
},
使用打包命令之后发现打包出来文件是分包处理了
上述打包属于手动分包,如果这两个文件都用到了同一个包,那么会被打包两次,增加体积,这时候也需要对其进行配置来防止其对某一个包打包两次。
//更改配置选项
entry: {
main: {
import:"./src/main.js",
dependOn:"shared" //加上dependOn属性来指明要共享的包是哪一个
},
index: {
import:"./src/index.js",
dependOn:"shared"
},
shared:["axios"], //比如这两个入口依赖了axios,shared是键,数组里编写的是需要共享的包名,可以写多个,也可以再次添需要共享的包
//shared1:["vue"] //比如vue也需要共享
},
运行指令,查看打包结果
可以发现多出一个shared包,里面就是需要共享的包源码,并且需要共享的包名根据键来输出
2.动态导入
webpack提供了两种方式来实现动态导入:import()函数和require.ensure,但是后者已经不推荐使用了,所以本文着重介绍前者。
动态导入使用条件: 某一个包在运行的时候不确定会不会使用到,这时候可以使用动态导入,让其在使用到时候再进行加载 假设一个场景,浏览网页时,需要点击按钮来显示对应的页面,那我们可以编写这样的代码:
//假设加载这两个页面
import "./router/home"
import "./router/about"
// 不使用动态导入
const btn1 = document.createElement("button")
const btn2 = document.createElement("button")
btn1.innerText="btn1"
btn2.innerText="btn2"
document.body.appendChild(btn1)
document.body.appendChild(btn2)
打包之后是这样的:
可以看到并没有做分包处理,那些可能会使用到的包也被包含在主包里面被下载了,加大了主包体积。下载的时候也会将其一起下载下来,影响首屏渲染速度,而且相关页面还没有点击就开始渲染了,没有达到我们想要的效果:
这时使用动态加载方式加载:
const btn1 = document.createElement("button")
const btn2 = document.createElement("button")
btn1.innerText="btn1"
btn2.innerText="btn2"
// 使用动态导入用到的时候才会导入,提升首屏渲染速度
btn1.addEventListener("click",()=>{
import(/* webpackChunkName:"home" */"./router/home")
})
//import函数可以使用魔法注释来对打包之后的包名作限定
btn2.addEventListener("click",()=>{
import(/* webpackChunkName:"about" */"./router/about") //这个函数的返回值是一个promise对象
})
document.body.appendChild(btn1)
document.body.appendChild(btn2)
也要对输出文件进行配置
output: {
clean: true,
filename: "[name].js",
path: path.resolve(__dirname, "./bundle"),
chunkFilename:"[name].js", //这里对输出之后的动态加载文件进行配置,name是占位符,
},
再次打包,查看打包结果:
可以看到打包之后多出两个文件,即动态加载的文件,这样那些可能会使用到的包不会包含在主包里面被下载了只有在点击的时候才会将其对应页面下载并且渲染:
这里只有主包被下载下来,接下来点击btn1,查看结果:
可以发现home被下载和渲染
3.splitChunk模式自定义分包
使用webpack自带的插件splitchunkplugin,它默认只对异步请求才会进行分包(比如动态导入),其他的包会放在主包里面(比如第三方库等),会加大主包体积,如果希望第三方包也单独打包一个包,可以在optimization配置里面进行配置:
optimization:{
chunkIds:"deterministic", //使用这个和下面id占位符相对应,是一个确定的值,不会在编译的时候出现问问题或者变化
splitChunks:{
chunks:"all", //对所有的第三方包进行分包处理
maxsize:200000 //如果对于拆分出来的包太大,还可以设置上限和下限来继续进行拆包
minsize:100000
},
这里我使用了react库的代码进行分包
运行打包命令输出结果:
可以看到输出两个包,打开文件所在位置查看包体积:
可以看到有一个包体积在上下限之间,还有一个包远远大于最大值,是因为包内代码不可以再拆分
如果对输出的包名不满意,可以再对其进行配置:
optimization:{
runtimeChunk: {
name: "runtime"
},
//runtime也可以分离在独立包里面,默认是打包在主包里面
splitChunks:{
chunks:"all", //对所有的第三方包进行分包处理
maxSize:200000, //默认分包大小为20kb,大于这个阈值才会进行拆包
minSize:100000,
//minSize默认是20kb,如果小于这个值不会进行分包
cacheGroups:{
// 这里自定义分包进行配置
vandor:{ //键任意名字即可
test:/[\\/]node_modules[\\/]/,//使用正则表达式来匹配需要分包的文件,这里是来着这个文件下的包都要分包
filename:"abc_vandor.js",
//输出的包名进行配置
chunks:"all"
},
}
},
再次运行查看结果:
可以看到第三方包分离出来了并且有我们设置的名称
runtime也可以分离在独立包里面,默认是打包在主包里面的:
运行打包命令,之后查看,可以发现runtime被打包到独立文件里面了:
二、提取css文件
我们在loader里面配置了loader之后,就可以引入css文件,但是css文件并不是以单独文件打包的,而是存放在html文件的style标签内,这里我编写了一些css样式并将其引入:
输入打包命令之后查看结果:
可以看到css文件并不是单独成一个文件,点开浏览器可以发现css文件都编写在style标签里面:
因为style-loader将其放在了style标签中,这是不利于页面的初渲染的,因为我们都知道将浏览器将html下载之后就要对标签进行解析,不利于用户体验,这时候可以使用到MiniCssExtractPlugin将css文件独立出来。
独立出来的好处是有很多的,使用这插件会生成link标签在head区域,这样我们可以对样式文件异步加载,而不是立即解析。
那接下来对插件进行配置吧~
plugins: [
new MiniCssExtractPlugin({
// 这里编写打包之后的css文件命名等
filename:"css/[name].css", //将其打包在build下的css文件夹下
chunkFilename:"css/[name].css" //异步css文件导入生成独立包
}),
]
//同时对webapck生成的js文件进行分包
output: {
clean: true,
path: path.resolve(__dirname, './build'),
filename: 'js/[name]-bundle.js',
chunkFilename: 'js/[name]_chunk.js',
},
//将style-loader换成MiniCssExtractPlugin
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: "babel-loader",
}
},
{
test: /\.ts$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use:[
// 'style-loader'开发环境使用
MiniCssExtractPlugin.loader //生产环境使用,会生成独立的包
,'css-loader'
]
}
]
},
运行打包命令查看结果
可以看到这里css文件和js文件分离
打包之后的上线速度
一、js代码压缩丑化
大家平时在下载源码的时候,下载出来打开可能会是这个样子:
肯定会想,这是啥呀,明明自己写出来的代码,咋看不懂。这是因为使用了terser插件对代码进行了压缩和丑化,删除了无用的空格,使用较少的字符替代原来的语义化变量等,就变成了这样咯。 terser插件需要安装,我们安装之后就可以对其进行配置。
这个插件的配置需要在optimization里面配置:
//插件记得引入
optimization: {
chunkIds: 'deterministic',
minimize: true,
splitChunks: {
chunks: "all", //当需要分包的时候可以打开
// 当一个包大于指定的大小时, 继续进行拆包
// maxSize: 20000,
// // 将包拆分成不小于minSize的包
// minSize: 10000,
minSize: 10,
// 自己对需要进行拆包的内容进行分包
cacheGroups: {
utils: {
test: /utils/,
filename: "js/[id]_utils.js"
},
vendors: {
// /node_modules/
// window上面 /\
// mac上面 /
test: /[\\/]node_modules[\\/]/,
filename: "js/[id]_vendors.js"
}
}
},
// 代码优化: TerserPlugin => 让代码更加简单 => Terser
minimizer: [
// JS代码简化
new TerserPlugin({
extractComments:false, //抽取注释,不会将项目里面的注释生成独立文件
terserOptions:{
compress:{
arguments:true, //将参数也压缩
}
}
})
]
},
配置完成之后运行打包命令查看输出的文件
可以看到生成的代码被压缩和丑化,就连空格也不放过。(热知识:空格也会占用储存空间)
二、css代码压缩丑化
前面说到js文件可以进行压缩,那么那么那么css肯定也可以!但是css不能丑化哈,只能将空格啥的删除,压缩的插件都需要在optimization里面配置
minimizer: [
// JS代码简化
new TerserPlugin({
extractComments:false, //抽取注释,不会将项目里面的注释生成独立文件
terserOptions:{
compress:{
arguments:true, //将参数也压缩
}
}
}),
new CssMinimizerPlugin()
//更加具体的配置大家可以去webpack官网上看看哈
]
运行打包命令查看结果:
绿色字体是文件来源。可以看到css文件空格都被删除了,如果出现重复类名什么的也会被删除
这个是原来的样式
三、html压缩丑化
是的,html也可以进行压缩
minimizer: [
// JS代码简化
new TerserPlugin({
extractComments:false, //抽取注释,不会将项目里面的注释生成独立文件
terserOptions:{
compress:{
arguments:true, //将参数也压缩
}
}
}),
new CssMinimizerPlugin()
//更加具体的配置大家可以去webpack官网上看看哈
new HtmlMinimizerPlugin()
]
运行查看打包结果:
可以看到空格换行什么的都被压缩了
没有了
好啦,关于webpack优化就说这么多啦,有关更多的配置要在官网上查看,大家也要多阅读官网上的文档啊!上面提到的压缩,在开启生产环境的时候,terser和html压缩会在生产环境下自动开启,但是css压缩要自己配置。对了,记得下载插件和引入~