前提:vue-cli3.0,pc端项目,首屏加载时间较长
问题: 影响首屏时间长的主要问题:大文件的加载长,导致首屏加载的时间长
解决思路:
- 减少非必须大文件的加载;
- 因为浏览器可以并行加载文件,大文件可以分割为小文件加载;
一、问题定位:
首先通过谷歌浏览器调试去发现导致首屏加载时间较长的原因:
通过上面两张图可以看出来, 1. 字体包文件很大,2. chunk-vendors文件很大,3. 首屏会加载很多图片;
二、问题解决:
-
首屏加载很多图片
vue项目中有两个地方可以存放图片,一个是public文件夹(这个文件夹下面的资源不会进行编译,处理,适合存放背景图之类的大图片),二是src下面的asset文件夹,在这个文件夹下面存放的图片在打包之后会经过编译和压缩处理变成base64格式的数据,(这个文件夹适合存放小而且多的图片,这样在首屏加载的时候只需要加载已经编译好的图片即可)
所以优化的第一步就是将图片放在合适的文件夹下面
优化之后的首屏加载图片:
-
字体包文件很大
2.1 首先整理必须的字体,删掉不需要的字体,因为很多字体包会有很多种字体(粗体,正常等),其实项目中并不完全需要2.2 其次我在网上找了一些字体优化的解决方案,但是我发现很多方案的原理是将html中用到的文字对应生成一个font,但是对于根据动态数据生成的页面就不行了,方案不适合~~~
chunk-vendors文件很大
3.1 首先使用打包分析工具分析打包之后的文件结构 (工具:webpack-bundle-analyzer),安装和配置参考:https://www.cnblogs.com/ttjm/p/11724230.html
我的项目分析结果如下:(这个时候我已经配置了路由懒加载,这个部分的配置参照3.3)
Tips:从这个图上可以清晰的看出 chunk-vendors 文件的组成是由 node_modules打包形成的;
3.2 (原理)在vue-cli3 对应的 we的默认是带有代码分割的配置的,上图形成的分割是由默认配置的代码分割方式形成的;
默认的配置如下(按照一定的规则对项目中的项目进行拆分和打包)
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', //三选一:"initial" 初始化,"all"(默认就是all),"async"(动态加载)
minSize: 30000, // 形成一个新代码块最小的体积,只有 >= minSize 的bundle会被拆分出来
maxSize: 0, //拆分之前最大的数值,默认为0,即不做限制
minChunks: 1, //引入次数,如果为2 那么一个资源最少被引用两次才可以被拆分出来
maxAsyncRequests: 5, // 按需加载的最大并行请求数
maxInitialRequests: 3, // 一个入口最大并行请求数
automaticNameDelimiter: '~', // 文件名的连接符
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
从上面的默认配置可以看出,webpack是将 node_modules 底下的文件默认打包成了 chunk-vendors 文件,default中配置将将至少有两个chunk引入的模块进行拆分;
3.2 明白了 chunk-vendors 为什么这么大,下一步我们开始对 chunk-vendors 文件进行自定义的拆分,首先我们先确认将哪些文件拆分出来
方式一:按照文件大小进行拆分,按照如下的配置可以将依赖包超过300000bit的包单独拆分出来
// 拆包 (依赖包超过300000bit将被单独打包)
config.optimization.splitChunks({
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 300000, // 依赖包超过300000bit将被单独打包
automaticNameDelimiter:'-',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `chunk.${packageName.replace('@', '')}`;
},
priority:10
}
}
})
配置之后的打包结果
从图片上可以看到,红色区域都是超过 300000bit 的第三方库单独的打包出来,我们可以根据这个打包文件重新进行配置,将这个文件配置出来
对应的配置是
function resolve(dir){
return path.join(__dirname, dir)
}
config.optimization = {
splitChunks:{
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
// 如果想echarts单独打包出来 可以通过当前方式设置 其他后引入的依赖也可以通过这种方式单独打包成一个js文件
echarts: {
name: 'chunk-echarts', // split vantUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?echarts(.*)/ // in order to adapt to cnpm
},
lodash: {
name: 'chunk-lodash',
priority: 30,
test: /[\\/]node_modules[\\/]_?lodash(.*)/
},
zrender: {
name: 'chunk-zrender',
priority: 50,
test: /[\\/]node_modules[\\/]_?zrender(.*)/
},
html2canvas: {
name: 'chunk-html2canvas',
priority: 60,
test: /[\\/]node_modules[\\/]_?html2canvas(.*)/
},
jspdf: {
name: 'chunk-jspdf',
priority: 70,
test: /[\\/]node_modules[\\/]_?jspdf(.*)/
},
element: {
name: 'chunk-element',
priority: 80,
test: /[\\/]node_modules[\\/]_?element-ui(.*)/
},
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
priority: 10
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
}
}
这里,你可能会出现一个问题,浏览器警告的大概意思是有一些文件你没有预先导入,这个是因为vue-cli中默认配置了打包之后预先加载的文件,你在拆分了文件之后,新生成的文件他就无法正常的导入了,这里需要配置
// chunks
module.exports = {
pages: {
index: {
entry: 'src/main.js',
template: 'public/index.html',
filename: 'index.html',
title: 'xxxx风险地图',
chunks: ['chunk-vendors', 'chunk-commons','chunk-echarts','chunk-lodash', 'chunk-zrender','chunk-html2canvas','chunk-jspdf','chunk-element', 'index'] // 需要引入 splitChunks 中的文件
// 需要引入 splitChunks 中的文件
}
}
}
}
优化之后的首屏加载vendors:(这里如果你觉得文件还是很大,可以按照上面的步骤minSize改小,将更多的第三方库拆分出来)
方式二: 你可以根据 webpack-bundle-analyzer 工具生成的那张图上的内容进行拆分,配置方式同上;
大文件拆分之后的包结构
Tips: 可以看出拆分之后不需要经常改动的第三方库的包被单独的打包成一个文件,减少了vendors 的体积,而且在模块之后复用的时候更加方便;(这里只进行了node_modules文件夹的拆分,业务代码的拆分你可以根据复用度或者是模块之间的关系进行拆分,更加详细的配置可以在webpack官网查找splitChunks的配置参数)
3.3 路由懒加载-优化首屏加载效率
- npm安装 @babel/plugin-syntax-dynamic-import 插件
// babel.config.js 文件
module.exports = {
"presets": [
"@vue/app"
],
"plugins": [
[
'@babel/plugin-syntax-dynamic-import'
]
]
}
- // 首先修改路由配置,使用webpackChunkName给路由分组命名,webpackChunkName相同的会打包到同一个文件中去
import Vue from "vue";
import VueRouter from "vue-router";
// 优化首屏加载时间
const Home = () => import(/* webpackChunkName: "Home" */ '../views/Home.vue');
const RealTimeMonitoring = () => import(/* webpackChunkName: "RealTimeMonitoring"*/ '../views/child/RealTimeMonitoring.vue');
Vue.use(VueRouter);
const HistoryAnalyse = () => import(/* webpackChunkName: "HistoryAnalyse" */ '../views/child/HistoryAnalyse.vue');
const routes = [
{
path: '/',
redirect: '/Home'
},
{
path: "/Home",
name: "Home",
component: Home,
redirect: "/Home/RealTimeMonitoring",
children: [
{
path: '/Home/HistoryAnalyse', // 历史分析
name: 'HistoryAnalyse',
component: HistoryAnalyse,
},
{
path: '/Home/RealTimeMonitoring', // 实时监测
name: 'RealTimeMonitoring',
component: RealTimeMonitoring,
},
}
];
修改之后路由懒加载之后的打包文件(你会发现打包之后的文件更加清晰了)
但是首屏的时候还是加载了所有的打包文件(其实我只想要在首屏加载RealTimeMonitoring文件)这是因为vue-cli3 里面默认打开了 preload,prefetch,所以你需要在配置中将这个关闭
// vue.config.js 文件中
// 官网默认配置
chainWebpack: (config) => {
config.plugins.delete('preload')
config.plugins.delete('prefetch')
},
// 经过测试这是不正确的
// 正确的配置如下(index对应的是pages中的名字)
chainWebpack: (config) => {
config.plugins.delete('preload-index')
config.plugins.delete('prefetch-index')
},
好了,现在看下配置正确之后(首屏已经没有加载HistoryAnalyse文件了,完成)
当点击了历史模块的时候,历史模块的数据才被加载
三、另外一些优化方式
- 开启gzip压缩
- 因为需要前后端共同配合还没有尝试
- 待补充学习~~
四、问题回顾
在进行优化的过程中,其实在网上找了很多的方案,最终确认修改的是上面的内容,不过有一些优化方案也值得分享一下:
- CDN替换依赖包引入
原理:项目打包时会根据依赖关系自动打包压缩依赖文件,当依赖文件过大,会导致首屏加载变慢,使用CDN: 简单来说就是可以使用CDN引入依赖库,减少依赖包体积,释放服务器压力, 它由距离最近的缓存服务器直接响应,提高加载速度
缺点:对网络和库放置的地址要求比较高,如果在内网不行;(个人感觉没有包依赖方便) - 组件库(element-ui、iview等)按需引入;
缺点:比较麻烦,建议在项目结尾的时候在修改
参考:SPA 首屏加载性能优化之 vue-cli3 拆包配置
https://www.jianshu.com/p/f9f796750484