首先我们来看没有优化之前的耗时时间:
在网络比较好的情况下, 耗时:6.86s。 平均是 7s 左右。
首屏时间(First Contentfull Paint): 指的是响应用户在浏览器上输入URL网址, 到首屏内容渲染完后才能的一个时间。此时整个网页不一定要渲染完成,但需要展示当前视窗需要的内容。
可以通过DOMContentLoad
或者是 performance
来计算首屏时间.
windown.addEventListner("DOMContentLoad", (event) => {
// ....
})
performance.getEntriesByName("first-contentfull-paint")[0].startTime;
// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{
name: "first-contentful-paint",
entryType: "paint",
startTime: 507.80000002123415,
duration: 0,
};
我们知道 vue, react 等框架都是js 渲染的html。是典型的单页应用,首次加载耗时多,因此优化Vue项目首屏加载对于提升用户体验非常重要。所以必须要等到这个js文件加载完成后界面才会显示。
Vue-Router路由懒加载
也叫延迟加载,即在需要的时候进行加载,随用随载。
在我们的vue 项目中:
总结的来说:只有在这个路由被访问到的时候,才加载对应的组件,否则不加载!
路由懒加载,在访问到当前页面才会加载相关的资源,异步方式分模块加载文件
我们使用ES6标准语法import()来实现懒加载
没有用到路由加载懒加载之前是这么写的
import page404 from "@/views/error/page404.vue";
const routes = [
{
path: "/page404",
name: "page404",
meta: {
title: "404"
},
components: {
slideMenu: slideMenu,
topBar: topBar,
content: page404
}
},
];
export default routes;
使用路由懒加载:
const page404 = () => import("@/views/error/page404.vue");
我们先看打包之后下面的img 的图片的大小:
总的5.6M。 有点大.
我们使用 vue inspect > output.js导出 vue-cli 做的的默认webpoack配置:
发现对于图片, 只用到了 url-loader 。 相对一些比较大的图片。是可以进行压缩的。可以使用 image-webpack-loader
我们在 vue-config.js配置:
const chainWebpack = function chainWebpacks (config) {
config.module
.rule("images")
.use("image-webpack-loader")
.loader("image-webpack-loader")
.options({
bypassOnDebug: true
})
.end();
};
然后再进行npm run build打包在看看img:
图片的大小从 5.8M 变到了 1.8M。 感觉效果还是明显的。
经过上面这两步优化,我们再看来首屏加载的时间:
首页加载的数据的耗时明显减少了大概 1/2 时间, 棒棒哒
从上面我们可以看出:vendor-chunks.js 很大。当我们的项部署了之后, 我们的资源文件请求会保持原来的大小。如果文件过大,并且很多的情况下,会导致网络请求耗时。严重点可能会阻塞后面的进程。针对这样的情况, 我们有没有什么比较好的解决方法呢? 有的, 那就进行 gzip压缩。
gzip压缩有两种方式:
那我们先来看看这两种方式:
这种方式是浏览器请求文件时,服务器对该文件进行压缩后传输给浏览器。前端不用做任何的配置,不需要 webpack生成 .gz文件。而是服务器自己处理。就拿 Nginx 来举例,我么打开 nginx.conf 文件, 会有默认配置,默认的 #gzip on;即不打开。
nginx 文件结构
... # 全局块
events { # events块
...
}
http # http块
{
... # http全局块
server # server块
{
... # server全局块
location [PATTERN] # location块
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... # http全局块
}
在 http 块这里开启 gzip和相关的配置:
http {
# ... 已省略
# 开启gzip
gzip on;
# 设置缓冲区大小
gzip_buffers 4 16k;
#压缩级别官网建议是6
gzip_comp_level 6;
#压缩的类型
gzip_types text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php;
# ... 已省略
}
这种方案的特点:使用nginx在线gzip,缺点就是耗性能,需要实时压缩,但是vue打包后的文件体积小。
这次优化主要是采用这种方式。
这种方式是打包的时候通过 webpack配置生成对应的.gz文件,浏览器请求文件时,服务器返回相应的的文件的 .gz 文件。
安装 compression-webpack-plugin
npm i compression-webpack-plugin -D
然后再vue.config.js中设置
const CompressionPlugin = require("compression-webpack-plugin");
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
// ... 已省略
plugins: [
// ... 已省略
new CompressionPlugin({
test: productionGzipExtensions, // 所有匹配此{RegExp}的资产都会被处理
threshold: 512, // 只处理大于此大小的资产。以字节为单位
minRatio: 0.8, // 只有压缩好这个比率的资产才能被处理
deleteOriginalAssets: false // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
})
]
启用gzip压缩打包之后,会变成下面这样,自动生成gz包。目前大部分主流浏览器客户端都是支持gzip的,就算小部分非主流浏览器不支持也不用担心,不支持gzip格式文件的会默认访问源文件的,所以不要配置清除源文件。所以这时候打包的总体积会变大, 是因为我们没有删除源文件。是为了防止有些浏览器不支持的时候能返回源文件。
上面test匹配的压缩文件类型, 并没有对图片进行压缩,因为图片压缩并不能实际减少文件大小,反而会导致打包后生成很多同大小的gz文件,得不偿失。
这种方式是浏览器在请求资源时,服务器返回相应的 .gz 文件。 所以需要在服务器配置一个属性, 期望它能够正常返回我们需要的.gz文件
ginx举例(nginx.conf文件):
http {
# ...已省略
# 静态加载本地的gz文件。
gzip_static on;
}
其中gzip_static on这个属性是静态加载本地的gz文件
我们先来看采用这种方法前的请求的chunk-vendors.js的大小:
我们可以看到请求的这个文件大小有 5.4 MB。
我们采用gzip压缩之后,请求该文件的大小:
可以看出来,请求文件的大小从 5.4MB 变成了 854KB。 而首页的加载时间较少的幅度不是很大, 但也是减少了。
nginx配置了静态gz加载后,浏览器也返回的是gz文件,这样就会请求小文件而不会导致请求卡线程,并且,因为保留了源文件,所以当我们删除gz后,浏览器会自动去请求原始文件,而不会导致界面出现任何问题
静态加载gz文件主要是依托于下面的请求头:
这种优化的主要特点: webpack打包,然后直接使用静态的gz,缺点就是打包后文件体积太大,但是不耗服务器性能。
从图上我们发现首屏加载过程中总公发起了149次请求。这显然是不友好的。那我们可以考虑考虑减少首屏加载的请求次数。
减少首屏加载请求次数可以从下面这个方面入手:
SplitChunks插件是什么呢,简单的来说就是Webpack中一个提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,拆分过大的js文件,合并零散的js文件。 在Webpack出现前,提取公共代码是人为去处理,而SplitChunks插件的作用就是通过配置让Webpack去帮你提取公共代码
用SplitChunks插件来控制Webpack打包生成的js文件的内容的精髓就在于,防止模块被重复打包,拆分过大的js文件,合并零散的js文件。 最终的目的就是减少请求资源的大小和请求次数
我们先来看splitChunks的一下字段说明吧
const path = require("path");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// ...
optimization: {
splitChunks: {
// 表示选择哪些 chunks 进行分割,可选值有:async,initial和all, 默认:async
chunks: "all",
// 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
minSize: 3000,
// 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
minChunks: 1,
// 表示按需加载文件时,并行请求的最大数目。默认为5。
maxAsyncRequests: 1,
// 表示加载入口文件时,并行请求的最大数目。默认为3。
maxInitialRequests: 3,
// // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
automaticNameDelimiter: "~",
// // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
// name: true,
// 缓存组: cacheGroups 的配置项跟 splitChunks是一样的, 但是它自己有几个自己的配置项
cacheGroups: {
vender: {
// 优先级:数字越大优先级越高,因为默认值为0,所以自定义的一般是负数形式
priority: -10,
// test:可以是一个函数也可以是一个正则,函数的返回值是:boolean RegExp string,通过返回值或者正则来进行匹配。
test: /[\\/]node_modules[\\/]/,
},
default: {
// type: "json",
minChunks: 2,
priority: -20,
// 这个的作用是当前的chunk如果包含了从main里面分离出来的模块,则重用这个模块,这样的问题是会影响chunk的名称。
reuseExistingChunk: true
}
}
}
}
}
使用webpack-bundle-analyzer
进行体积分析。该插件可生成依赖包形成可视化分析图谱,帮组开发者分析项目结构
npm install --save-dev webpack-bundle-analyzer
将插件添加到webpack中,因为使用的是vue-cli,所以应在vue.config.js中添加配置:
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const DEV = process.env.NODE_ENV !== "production";
const chainWebpack = function chainWebpacks (config) {
// ...
if (DEV) {
// 开发环境使用
config.plugin("compressionPlugin")
.use(new BundleAnalyzerPlugin())
.end();
};
};
运行npm run build生成分析页面
从分析页面可以看出 chunk-vendor
很大。因为这个chunk是项目所有的依赖库,从它是打包了node_modules可以看出,所以很影响性能。从图中发现monaco-editor 在线编辑器占了很大体积。 我们可以把它抽离出来。因为用的地方太多了, 导致很难做成按需加载了的了。
在vue.config.js配置:
config.optimization.splitChunks({
cacheGroups: {
common: {
name: "chunk-common",
chunks: "all",
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
priority: 1,
reuseExistingChunk: true
},
vendors: {
name: "chunk-vendors",
test: /[\\/]node_modules[\\/]/,
chunks: "all",
priority: 2,
reuseExistingChunk: true,
enforce: true
},
// 增加一个echarts cacheGroup
echarts: {
name: "chunk-echarts",
test: /[\\/]node_modules[\\/]echarts[\\/]/,
priority: 3,
chunks: "all",
reuseExistingChunk: true,
enforce: true
},
// 增加一个monacoEditor cacheGroup
monacoEditor: {
name: "chunk-monaco-editor",
test: /[\\/]node_modules[\\/]monaco-editor[\\/]/,
chunks: "all",
priority: 4,
reuseExistingChunk: true,
enforce: true
}
}
});
我们再来看下分析图:
可以看出monaco-editor和echarts被单独打包了。
现在我们来看最终的结果:
请求次数从 149 减到了 115 次。可观的是首页加载耗时减到了 1.74s。 平均值大概 1.5 s。 表示还是可观的。
注意: SplitChunks插件对代码作更细致的拆分 需要注意的减少请求数必然使得单个文件体积变大,二者是矛盾的,最佳实践是取得一个中庸的值平衡优劣.
优化方法 | 请求数量 | 耗时时间 |
---|---|---|
未做任何优化 | 149次 | 6.86s |
路由懒加载+压缩图片 | 149次 | 4.26s |
splitChunks分离代码 | 115次 | 1.74s |
原地址: vue项目首屏加载优化