在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的构建速度和网站加载速度都会有比较大的影响。单页应用会随着项目越大,导致首屏加载速度很慢,针对目前所暴露出来的问题,有以下几种优化方案可以参考:
初步优化,减少全局组件引入(是否放在main.js),按需引入需要的模块(echarts按需引入等),使用轻量级数据库(moment.js 切换 data-fns等)
路由懒加载和组件懒加载:
访问到当前页面才会加载相关的资源,异步方式分模块加载文件,默认的文件名是随机的id。如果在output中配置了chunkFilename,可以在component中添加WebpackChunkName,是为了方便调试,在页面加载时候,会显示加载的对应文件名+hash值,如下图:
- {
- path: '/Login',
- name: 'Login',
- component: () = >import( /* webpackChunkName: "Login" */ '@/view/Login')
- }
图片懒加载:使用vue-lazyload插件
- //引入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
- })
gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。html、js、css文件甚至json数据都可以用它压缩,可以减小60%以上的体积。
前端配置gzip压缩,并且服务端使用nginx开启gzip,用来减小网络传输的流量大小。
webpack打包时借助 compression webpack plugin实现gzip压缩,安装插件如下:
npm i -D compression-webpack-plugin
在vue-cli 3.0 中,vue.config.js配置如下:
- const CompressionPlugin = require('compression-webpack-plugin');//引入gzip压缩插件
- module.exports = {
- plugins:[
- new CompressionPlugin({//gzip压缩配置
- test:/\.js$|\.html$|\.css/,//匹配文件名
- threshold:10240,//对超过10kb的数据进行压缩
- deleteOriginalAssets:false,//是否删除原文件
- })
- ]
- }
启用gzip压缩打包之后,会变成下面这样,自动生成gz包。目前大部分主流浏览器客户端都是支持gzip的,就算小部分非主流浏览器不支持也不用担心,不支持gzip格式文件的会默认访问源文件的,所以不要配置清除源文件。
在nginx中开启gzip:
- server {
- gzip on;
- gzip_buffers 32 4K;
- gzip_comp_level 6;
- gzip_min_length 100;
- gzip_types application/javascript text/css text/xml application/json;
- gzip_vary on;
-
- listen 80;
- listen [::]:80 ;
- 。。。。。。。。
配置好之后,打开浏览器访问线上,F12查看控制台,如果该文件资源的响应头里显示有Content-Encoding: gzip,表示浏览器支持并且启用了Gzip压缩的资源
生产环境是内网的话,就把资源放内网,通过静态文件引入,会比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="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';
在vue.config.js配置:
- module.exports = {
- productionSourceMap: false, // 生产环境是否生成 sourceMap 文件,一般情况不建议打开
- }
在设置了productionSourceMap: false之后,就不会生成map文件,map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。也就是说map文件相当于是查看源码的一个东西。如果不需要定位问题,并且不想被看到源码,就把productionSourceMap 置为false,既可以减少包大小,也可以加密源码。
打包之后控制台很干净,部署正式环境之前最好这样做。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
- configureWebpack: config => {
- if (isProduction) {
- .....
- config.plugins.push(
- new UglifyJsPlugin({
- uglifyOptions: {
- compress: {
- warnings: false,
- drop_debugger: true,
- drop_console: true,
- },
- },
- sourceMap: false,
- parallel: true,
- })
- )
- }
- }
使用插件: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'
- })
- })
- ]
- };
- }
严格说来这一步不算在编码技术范围内,但是却对页面的加载速度影响很大。对于所有的图片文件,都可以在一个叫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()
- },
合理使用v-if和v-show
合理使用watch和computed
使用v-for必须添加key, 最好为唯一id, 避免使用index, 且在同一个标签上,v-for不要和v-if同时使用
定时器的销毁。可以在beforeDestroy()生命周期内执行销毁事件;也可以使用$once这个事件侦听器,在定义定时器事件的位置来清除定时器。详细见vue官网
长列表性能优化
上边已经讲述了优化问题,把 所 有 的 优 化 都 做 完 之 后 , 加 载 速 度 有 了 显 著 提 升,把所有的优化都做完之后,加载速度有了显著提升}把所有的优化都做完之后,加载速度有了显著提升把所有的优化都做完之后,加载速度有了显著提升,但是再网慢的时候还是会有白屏,所以再白屏期间加骨架屏和loading就显得格外重要了。
- <body>
- //这里亲测有效,放心使用
- <div id="app">
- // 我们只需要再这里添加loading图或者骨架屏,有人会说怎么控制它的显示隐藏啊,
- //不用担心,再项目初始化完成后会自动替换为你的页面。
- <div class="self-loading">
- 页面正快马加鞭赶来,请耐心等待
- div>
- div>
- body>