在Vue项目中,引入到工程中的所有js、css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏。若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首屏的体验。可以看个例子:
这是优化前的页面加载状态:执行 npm run build
打包项目,出来的vendeor.js文件,基本都是1M以上的的巨大文件,没有用户能忍受5s以上的loading而不关闭页面的,如图所示:
当项目在挂载到服务器上,平均都是10S+以上加载出来,好家伙这加载时间,仿佛过了半个世纪,很烦人,心态boom, 开发者甚至都有种想砸电脑的冲动
一、分析下前端加载速度慢原因
第一步:首先安装webpack的可视化资源分析工具,命令行执行:
npm i webpack-bundle-analyzer -D
第二步:然后在webpack的dev开发模式配置中,引入插件,代码如下:
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') plugins: [ new BundleAnalyzerPlugin() ]
第三步:最后命令行执行 npm run build --report
, 浏览器会自动打开分析结果,如下所示:
可以看到vue全家桶相关依赖占用了很大的空间,对webpack的构建速度和网站加载速度都会有比较大的影响。单页应用会随着项目越大,导致首屏加载速度很慢,针对目前所暴露出来的问题,有以下几种优化方案可以参考:
二、优化方案
0.初步优化
初步优化,减少全局组件引入(是否放在main.js),按需引入需要的模块(echarts按需引入等),使用轻量级数据库(moment.js 切换 data-fns等)
1.采用异步组件和懒加载的方式
异步组件:
这样做的目的是在首屏渲染时减少加载文件的数量,在需要用到一些组件时才会从服务器获取,而且不会重复请求,可以有效的减少白屏的时间。
路由懒加载:
访问到当前页面才会加载相关的资源,异步方式分模块加载文件,默认的文件名是随机的id。如果在output中配置了chunkFilename,可以在component中添加WebpackChunkName,是为了方便调试,在页面加载时候,会显示加载的对应文件名+hash值,如下图:
{ path: '/Login', name: 'Login', component: () = >import( /* webpackChunkName: "Login" */ '@/view/Login') }
图片懒加载:使用vue-lazyload插件
Vue 图片懒加载 之 Vue-Lazyload-CSDN博客
//引入vue懒加载 import VueLazyload from 'vue-lazyload' //方法一: 没有页面加载中的图片和页面图片加载错误的图片显示 // Vue.use(VueLazyload) //方法二: 显示页面图片加载中的图片和页面图片加载错误的图片 //引入图片 import loading from '@/assets/images/load.jpg' //注册图片懒加载 Vue.use(VueLazyload, { // preLoad: 1.3, error: '@/assets/images/error.jpg',//图片错误的替换图片路径(可以使用变量存储) loading: loading,//正在加载的图片路径(可以使用变量存储) // attempt: 1 })
使用:
<div class="lazyLoad"> <ul> <li v-for="img in arr"> <img v-lazy="img.thumbnail_pic_s"> </li> </ul> </div>
2.webpack开启gzip压缩文件传输模式
gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。html、js、css文件甚至json数据都可以用它压缩,可以减小60%以上的体积。
前端配置gzip压缩,并且服务端使用nginx开启gzip,用来减小网络传输的流量大小。
webpack打包时借助 compression webpack plugin实现gzip压缩,安装插件如下:
npm i compression-webpack-plugin 或 npm install compression-webpack-plugin@6.1.1 --save-dev
在vue-cli 3.0 中,vue.config.js配置如下:
const CompressionPlugin = require('compression-webpack-plugin');//引入gzip压缩插件 module.exports = { plugins:[ new CompressionPlugin({//gzip压缩配置 filename: '[path][base].gz', algorithm: 'gzip', // 压缩算法,官方默认压缩算法是gzip test:/\.js$|\.css$|\.html$|\.eot$|\.woff$/,// 使用gzip压缩的文件类型 threshold:10240,//对超过10kb的数据进行压缩,默认是10240 deleteOriginalAssets:false,//是否删除原文件 minRatio: 0.8, // 最小压缩比率,默认是0.8 }) ] }
启用gzip压缩打包之后,会变成下面这样,自动生成gz包。目前大部分主流浏览器客户端都是支持gzip的,就算小部分非主流浏览器不支持也不用担心,不支持gzip格式文件的会默认访问源文件的,所以不要配置清除源文件。
在nginx中开启gzip:
server{ //开启和关闭gzip模式 gzip on; //gizp压缩起点,文件大于2k才进行压缩;设置允许压缩的页面最小字节数,页面字节数从header头得content-length中进行获取。 默认值是0,不管页面多大都压缩。建议设置成大于2k的字节数,小于2k可能会越压越大。 gzip_min_length 2k; // 设置压缩所需要的缓冲区大小,以4k为单位,如果文件为7k则申请2*4k的缓冲区 gzip_buffers 4 16k; // 设置gzip压缩针对的HTTP协议版本 gzip_http_version 1.0; // gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间 gzip_comp_level 2; //进行压缩的文件类型 gzip_types text/plain application/javascript text/css application/xml; // 是否在http header中添加Vary: Accept-Encoding,建议开启 gzip_vary on; }
配置好之后,打开浏览器访问线上,F12查看控制台,如果该文件资源的响应头里显示有Content-Encoding: gzip,表示浏览器支持并且启用了Gzip压缩的资源
3.Webpack 代码分割与优化
模块拆分: 配置Webpack将代码拆分成多个小块,利用Tree Shaking、代码压缩等技术减少代码体积。这将减少初始加载所需的下载时间,提高页面加载速度。
// vue.config.js module.exports = { configureWebpack: { optimization: { splitChunks: { chunks: 'all' } } } };
4.依赖模块采用第三方cdn资源(对于第三方js库的优化,分离打包)
生产环境是内网的话,就把资源放内网,通过静态文件引入,会比node_modules和外网CDN的打包加载快很多。如果有外网的话,可以通过CDN方式引入,因为不用占用访问外网的带宽,不仅可以为您节省流量,还能通过CDN加速,获得更快的访问速度。但是要注意下,如果你引用的CDN 资源存在于第三方服务器,在安全性上并不完全可控。国内的CDN服务推荐使用 BootCDN
目前采用引入依赖包生产环境的js文件方式加载,直接通过window可以访问暴露出的全局变量,不必通过import引入,Vue.use去注册
在webpack的dev开发配置文件中, 加入如下参数,可以分离打包第三方资源包,key为依赖包名称,value是源码抛出来的全局变量。对于一些其他的工具库,尽量采用按需引入的方式。
使用 CDN 的好处有以下几个方面
(1)加快打包速度。分离公共库以后,每次重新打包就不会再把这些打包进 vendors 文件中。
(2)CDN减轻自己服务器的访问压力,并且能实现资源的并行下载。浏览器对 src 资源的加载是并行的(执行是按照顺序的)。
第一步:修改vue.config.js
module.exports = { ... externals: { 'vue': 'Vue', 'vuex': 'Vuex', 'vue-router': 'VueRouter', 'axios': 'axios', 'element-ui': 'ELEMENT', 'underscore' : { commonjs: 'underscore', amd: 'underscore', root: '_' }, 'jquery': { commonjs: 'jQuery', amd: 'jQuery', root: '$' } } ... }
如果想引用一个库,但是又不想让webpack打包,且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals
第二步:在index.html中添加cdn
<link href="https://cdn.bootcss.com/element-ui/2.7.2/theme-chalk/index.css" rel="external nofollow" rel="stylesheet"> </head> <body> <div id="app"></div> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script> <script src="https://cdn.bootcss.com/vue-router/3.0.4/vue-router.min.js"></script> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.7.2/index.js"></script> <script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script> <script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore-min.js"></script> </body>
第三步:去除vue.use相关代码
通过 CDN 引入,在使用 VueRouter Vuex ElementUI 的时候要改下写法。CDN会把它们挂载到window上,可以不再使用Vue.use(xxx)
main.js中 注释掉
// import Vue from 'vue'; // import iView from 'iview'; // import '../theme/index.less';
5.禁止生成map文件
在vue.config.js配置:
module.exports = { productionSourceMap: false, // 生产环境是否生成 sourceMap 文件,一般情况不建议打开 }
在设置了productionSourceMap: false之后,就不会生成map文件,map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。也就是说map文件相当于是查看源码的一个东西。如果不需要定位问题,并且不想被看到源码,就把productionSourceMap 置为false,既可以减少包大小,也可以加密源码。
6.去掉代码中的console和debugger
打包之后控制台很干净,部署正式环境之前最好这样做。vue-cli3.0
configureWebpack: config => { if (process.env.NODE_ENV === 'production') { config.optimization.minimizer[0].options.terserOptions.compress.warnings = false config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = ['console.log'] } },
uglifyOptions去除console来减少文件大小
// 安装uglifyjs-webpack-plugin cnpm install uglifyjs-webpack-plugin --save-dev // 修改vue.config.js const UglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { configureWebpack: (config) => { if (process.env.NODE_ENV === 'production') { return { plugins: [ //打包环境去掉console.log new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false, drop_console: true, //注释console drop_debugger: true, //注释debugger pure_funcs: ['console.log'], //移除console.log }, }, }), ], } } } }
7. 预渲染配置
使用插件:prerender-spa-plugin
vue.config.js中配置如下:
const PrerenderSpaPlugin = require('prerender-spa-plugin'); const Render = PrerenderSpaPlugin.PuppeteerRenderer; const path = require('path'); configureWebpack: () => { if (process.env.NODE_ENV !== 'production') return; return { plugins: [ new PrerenderSPAPlugin({ // 生成文件的路径,也可以与webpakc打包的一致。 // 下面这句话非常重要!!! // 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。 staticDir: path.join(__dirname, 'dist'), // 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。 routes: ['/', '/Login', '/Home'], // 这个很重要,如果没有配置这段,也不会进行预编译 renderer: new Renderer({ inject: { foo: 'bar' }, headless: false, // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。 renderAfterDocumentEvent: 'render-event' }) }) ] }; }
提前加载: Vue 3的路由支持预加载功能,可在用户浏览站点时预先加载下一个页面所需的资源。这将确保用户切换页面时的迅速加载和呈现。
const routes = [ { path: '/home', component: () => import('./Home.vue'), meta: { preload: true } }, // 其他路由... ];
8.图片资源的压缩、icon资源使用、雪碧、代码压缩
严格说来这一步不算在编码技术范围内,但是却对页面的加载速度影响很大。对于所有的图片文件,都可以在一个叫tinypng的网站上去压缩一下。网址:tinypng.com/,对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力。然后通过操作CSS的background属性,控制背景的位置以及大小,来展示需要的部分。
// 图片压缩设置 chainWebpack: config => { // 图片打包压缩,使用了 --- image-webpack-loader --- 插件对图片进行压缩 config.module .rule('images') .use('image-webpack-loader') .loader('image-webpack-loader') .options({ bypassOnDebug: true }) .end() },
js代码压缩- - - -(webpack 自UglifyJsPlugin插件压缩js文件)
css 代码压缩- - - - (采用optimize-css-assets-webpack-plugin插件来压缩css代码)
9. 前端页面代码层面的优化
合理使用v-if和v-show
合理使用watch和computed
使用v-for必须添加key, 最好为唯一id, 避免使用index, 且在同一个标签上,v-for不要和v-if同时使用
定时器的销毁。可以在beforeDestroy()生命周期内执行销毁事件;也可以使用$once这个事件侦 听器,在定义定时器事件的位置来清除定时器。详细见vue官网
长列表性能优化
- 图片资源懒加载
- 前端接口防止重复请求实现方案-CSDN博客
- 用innerHTML代替dom操作,减少dom操作的次数,优化js性能
- 合理使用requestAnimationFrame动画代替setTimeOut
- 通过创建文档碎片 document.createDocumentFragment()-创建虚拟dom来更新dom
10.解决白屏,体验优化
把上面所有的优化都做完之后,加载速度有了显著提升,但是在网慢的时候还是会有白屏,所以再白屏期间加骨架屏和loading就显得格外重要了。
当我们的js加载完成之后我们的img就会被覆盖了,所以这种效果是特别好的,因为静态资源是在我们的项目当中直接有的,所以加载效率会特别的快,所以尽量让图片的大小变小这样会更好的提高项目效率