vue项目性能优化系列

Posted cenfengtao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue项目性能优化系列相关的知识,希望对你有一定的参考价值。

vue项目性能优化1,优化:使用webpack-bundle-analyzer分析vue的打包js文件大小
基于webpack 3、vue 2和vue-cli 2的性能优化。

所有的三方库都是打包到vendor.js文件。
注意:
vue-cli 2创建的项目,已经集成webpack-bundle-analyzer,可以直接运行npm run build --report。
vue-cli 3创建的项目,配置不一样。
1、在package.json文件,添加
“webpack-bundle-analyzer”: “^3.3.22、打开build/webpack.prod.conf.js文件,在
module.exports = webpackConfig
这一行的前面,添加
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

3、在config/index.js文件,添加
build: {
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// npm run build --report
// Set to true or false to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}

4、启动项目
npm run build --report

5、启动完成,打开浏览器
http://127.0.0.1:8888
开始分析,哪个文件比较大,就使用CDN加速。方块越大,表示js文件越大。重点优化红色、橙色的方块。

vue项目性能优化2,优化:使用CDN加速
基于webpack 3、vue 2和vue-cli 2的性能优化。

使用CDN加速,降低服务器带宽的压力。

externals的作用:
让在项目中通过import引入的依赖,在打包的时候,不会打包到vendor.js中,而是通过script的方式去访问这些依赖。

免费的CDN服务器:
https://www.bootcdn.cn/
https://cdnjs.com/
https://www.jsdelivr.com/

打开index.html文件,修改:
html2canvas 1.0.0-rc.5版本,只有一个CDN服务器,没有备份CDN,很可怕。只能降低版本,使用html2canvas 0.5.0-beta4版本。

 
<!--CDN加速,引用bootstrap的CDN资源-->
<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.10.0/index.js"></script>
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/qs/6.5.2/qs.min.js"></script>
<script src="https://cdn.bootcss.com/mint-ui/2.2.13/index.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@0.5.0-beta4/dist/html2canvas.min.js"></script>
<script src="https://cdn.bootcss.com/Swiper/4.5.1/js/swiper.min.js"></script>
<script src="https://cdn.bootcss.com/mobile-detect/1.4.4/mobile-detect.min.js"></script>
<!--这个没有备用CDN-->
<script src="https://cdn.jsdelivr.net/npm/vue-video-player@5.0.2/dist/vue-video-player.js"></script>

<script src="https://res2.wx.qq.com/open/js/jweixin-1.2.0.js"></script>

<script>
  //如果CDN加载失败,就加载备用的另一个CDN,防止页面打不开。
  window.Vue || document.write(‘<script src="https://cdn.jsdelivr.net/npm/vue@2.5.2/dist/vue.min.js"></script>‘);
  window.VueRouter || document.write(‘<script src="https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js"></script>‘);
  window.Vuex || document.write(‘<script src="https://cdn.jsdelivr.net/npm/vuex@3.1.0/dist/vuex.min.js"></script>‘);
  window.ELEMENT || document.write(‘<script src="https://cdn.jsdelivr.net/npm/element-ui@2.10.0/lib/index.js"></script>‘);
  window.axios || document.write(‘<script src="https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js"></script>‘);
  window.Qs || document.write(‘<script src="https://cdn.jsdelivr.net/npm/qs@6.5.2/dist/qs.js"></script>‘);
  window.MINT || document.write(‘<script src="https://cdn.jsdelivr.net/npm/mint-ui@2.2.13/lib/index.js"></script>‘);
  window.moment || document.write(‘<script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/min/moment.min.js"></script>‘);
  //这个CDN很特别,链接是0.5.0-beta4版本,但是,打开却是0.5.0-beta3版本
  window.html2canvas || document.write(‘<script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.min.js"></script>‘);
  window.Swiper || document.write(‘<script src="https://cdn.jsdelivr.net/npm/swiper@4.5.1/dist/js/swiper.min.js"></script>‘);
  window.MobileDetect || document.write(‘<script src="https://cdn.jsdelivr.net/npm/mobile-detect@1.4.4/mobile-detect.min.js"></script>‘);
</script>
打开build/webpack.base.conf.js文件,修改:
注意:
key-value格式,右边的value不是乱写的。举例来说,打开CDN的URL链接,看源码,会发现exports.MINT,所以,配置就写MINT。
左边的key,对应的是import Vue from ‘vue’里面的’vue’,不是乱写的。而import后面的Vue,和右边的value,不需要相同,没有关系。
module.exports = {
context: path.resolve(__dirname, ‘…/’),
entry: {
app: [“babel-polyfill”, “./src/main.js”]
},
//以下模块不打包到vendor.js文件,使用index.html文件中引用的CDN模块
externals: {
‘vue’: ‘Vue’,
‘vue-router’: ‘VueRouter’,
‘vuex’: ‘Vuex’,
‘element-ui’: ‘ELEMENT’,
‘axios’: ‘axios’,
‘qs’: ‘Qs’,
‘mint-ui’: ‘MINT’,
‘moment’: ‘moment’,
‘html2canvas’: ‘html2canvas’,
‘vue-video-player’: ‘VueVideoPlayer’,
‘swiper’: ‘Swiper’,
‘mobile-detect’: ‘MobileDetect’
}
}

优化前:vendor.js文件的大小是1.6 MB
优化后:vendor.js文件的大小是229 KB
对比:减少了1.3MB

注意:
import不要删除,否则会报错。
import axios from ‘axios’
不需要修改为
const axios = require(‘axios’)
官网有详细说明,参考:https://webpack.docschina.org/configuration/externals/

vue项目性能优化3,优化:路由懒加载
基于webpack 3、vue 2和vue-cli 2的性能优化。

让页面打开更快,解决白屏问题。
如果拆分的文件太多,会造成网络请求很多,这时,可以适当合并。可以使用import或者require.ensure()。

打开src/router/index.js文件,修改:
修改前:
import Vue from ‘vue’
import Router from ‘vue-router’

import login from ‘@/components/login’

Vue.use(Router)

export default new Router({
mode:‘history’,
routes: [
{
path: ‘/login’,
name: ‘login’,
component: login
}
]
});

修改后:
import Vue from ‘vue’
import Router from ‘vue-router’

Vue.use(Router)

export default new Router({
mode:‘history’,
routes: [
{
path: ‘/login’,
name: ‘login’,
component: resolve => require([’@/components/login’], resolve)
}
]
});

vue项目性能优化4,优化:gzip压缩
基于webpack 3、vue 2和vue-cli 2的性能优化。

gzip压缩,包括vue配置、nginx配置。

2.2.4.1、vue配置

compression-webpack-plugin参数说明:
https://www.html.cn/doc/webpack2/plugins/compression-webpack-plugin/

1、
打开config/index.js文件,修改:
productionGzip: true,
productionGzipExtensions: [‘js’, ‘css’],

2、
打开cmd命令窗口,依次执行:
G:
cd vueworkspace/wxVue
–注意,如果不想后面出错,建议跳过这一步
npm install --save-dev compression-webpack-plugin

3、
打开文件,修改:
这一步可以跳过,不需要修改。
把
asset: ‘[path].gz[query]’,
改成
filename: ‘[path].gz[query]’,

4、
运行报错:
TypeError: Cannot read property ‘emit’ of undefined

原因:
版本太高了,降低版本。
解决方法:
先卸载,再安装低版本。
打开cmd命令窗口,依次执行:
G:
cd vueworkspace/wxVue
–如果前面没有安装,就不需要执行卸载命令
npm uninstall --save-dev compression-webpack-plugin
npm install --save-dev compression-webpack-plugin@1.1.12
安装完成,打开package.json文件,可以看到"compression-webpack-plugin": “^1.1.12”,说明安装成功。

5、
运行打包,如果在控制台看到xxx.gz文件打印出来,说明gzip压缩的配置生效。(如果没有打印出来,需要检查是什么问题,“去除打包后静态资源文件名hash值”这个操作会导致gzip压缩无效,需要还原对hash值的修改。)

截图:
在这里插入图片描述

优化前:vendor.js文件的大小是1.6 MB
优化后:vendor.js文件的大小是229 KB
压缩后:vendor.js文件的大小是72.6 KB
对比:可以看到,相对于229 KB,压缩之后,大小减少了70%2.2.4.2、nginx配置
注意:修改nginx之后,要重启才生效。

gzip官网资料:http://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip
gzip_static官网资料:http://nginx.org/en/docs/http/ngx_http_gzip_static_module.html

服务器支持gzip的方式可以有两种:
1、打包的时候生成对应的.gz文件,浏览器请求xx.js时,服务器返回对应的xxx.js.gz文件
2、浏览器请求xx.js时,服务器对xx.js进行gzip压缩后传输给浏览器

ngx_http_gzip_module是nginx默认集成的,不需要重新编译,直接开启即可。

1、gzip_static配置优先级高于gzip
2、开启nginx_static后,对于任何文件都会先查找是否有对应的gz文件
3、gzip_types设置对gzip_static无效

gzip_static 在开始压缩创建硬盘上的文件之前,本模块将查找同目录下同名的.gz压缩文件,以避免同一文件再次压缩。

模块 ngx_http_gzip_static_module 允许发送以“.gz”作为文件扩展名的预压缩文件,以替代发送普通文件。
这个模块不是默认编译的,因此需要指定 --with-http_gzip_static_module 编译选项。

检查http_gzip_static_module模块是否安装
如果未安装,需要安装。
–执行以下命令,把所有的内容复制出来,粘贴到记事本,然后搜索“http_gzip_static_module”,如果能找到,说明已经安装了
nginx -V

如果nginx中使用了多层代理,则需要将gzip_http_version 1.0配置项开启,否则gzip配置不起作用; 原因分析: 如下图所示,当前浏览器中使用的http协议基本上是1.1版本,而nginx的upstream通信协议默认是http/1.0,gzip_http_version的默认值是1.1;因此如果配置多层代理时,如果没有配置gzip_http_version时,该值默认为1.1,而agent nginx会把请求转化为1.0版本的,此时gzip功能就会不起作用;将gzipz_http_version配置为1.0后,gzip功能正常。
总结一句话,多层代理使用gzip_http_version 1.0;

参数说明:
http: {
gzip on;
#预压缩,节省CPU。开启后如果能找到 .gz 文件,直接返回该文件,不会启用服务端压缩。webpack已经把文件压缩好了。
gzip_static on;
#启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 10k;
#选择1.0,则1.0和1.1都可以压缩(这个参数非常重要,特别是配置upstream之后,不能选择1.1)
gzip_http_version 1.0;
#设置压缩所需要的缓冲区大小
gzip_buffers 16 8k;
#gzip压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
gzip_comp_level 5;
#压缩文件类型(图片不压缩,很耗费CPU)
gzip_types text/plain text/css application/x-javascript application/javascript text/javascript;
#ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable “MSIE [1-6].”;
gzip_vary on;
}

–打开目录
cd /etc/nginx/conf.d

–正式环境、测试环境,配置有点不同
–测试环境
vi wx-t.test.com.conf
–测试机的正式环境(因为正式环境的入口也是在测试机)
vi wx.test.com.conf

–正式机的正式环境,找到location /wx/,添加gzip
vi /etc/nginx/nginx.conf

–因为服务器配置了SSL,所以,gzip配置在location里面。
–修改完成,保存退出(:wq命令)
gzip on;
gzip_static on;
gzip_min_length 10k;
gzip_http_version 1.0;
gzip_buffers 16 8k;
gzip_comp_level 5;
gzip_types text/plain text/css application/x-javascript application/javascript text/javascript;
gzip_disable “MSIE [1-6].”;
gzip_vary on;

–测试配置是否有错误,如果提示successful,就可以重启
nginx -t

–重启(重新加载配置)
–执行以下命令,遇到报错“The service command supports only basic LSB actions (start, stop, restart, try-restart, reload, force-reload, status). For other actions, please try to use systemctl.”
service nginx -s reload
–多了一个-s参数,上面的命令如果报错,就用下面的命令
service nginx reload 或者 systemctl reload nginx.service

–检测nginx是否重启
service nginx status

截图:
最后两个记录,显示是3月11日的操作,说明reload成功。
在这里插入图片描述
2.2.4.3、测试gzip是否生效
response headers中返回了Content-Encoding:gzip,表明gzip开启成功。
在这里插入图片描述

vue项目性能优化5,优化:CSS文件过大
基于webpack 3、vue 2和vue-cli 2的性能优化。

一般CSS文件不会过大,而且也没什么可优化的,毕竟CSS只是描述样式,没有逻辑,所以不会像JS出现重复引用的问题。
经过把css改成CDN加速之后,变小了一点,最后文件还是很大,打开文件查看,发现是图片的base64字符串被打包到css文件中了。不能继续优化了。
解决图片被转换成base64的问题,可以把图片上传到文件服务器,在项目里直接引用图片的链接,可以有效减小css文件的大小。
1、CDN加速

2、搜索整个项目,删除或者注释掉
import ‘element-ui/lib/theme-chalk/index.css’;
import ‘mint-ui/lib/style.css’
import “swiper/dist/css/swiper.min.css”;

尝试之后,失败的方案:
const cssCDN = [{
default: ‘https://cdn.bootcss.com/element-ui/2.10.0/theme-chalk/index.css3’,
backup: ‘https://cdn.jsdelivr.net/npm/element-ui@2.10.0/lib/theme-chalk/index.css’
}, {
default: ‘https://cdn.bootcss.com/mint-ui/2.2.13/style.min.css’,
backup: ‘https://cdn.jsdelivr.net/npm/mint-ui@2.2.13/lib/style.min.css’
}];

/**

CSS加载失败,就加载备份的CSS
元素的onerror方法,在微信模拟器不执行,
需要动态创建的link元素才可以。
@param data
*/
function cssLoad(data) {
let link;
for (let index in data) {
link = document.createElement(‘link’);
link.rel = ‘stylesheet’;
link.href = data[index].default;
console.log(’“onload” in node =’ + (“onload” in link))
link.onload = function() {
//css加载成功
};
link.onerror = function() {
//css加载失败,加载备份的CSS
let css = document.createElement(‘link’);
css.rel = ‘stylesheet’;
css.href = data[index].backup;
document.head.appendChild(css);
};
document.head.appendChild(link);
}
let timer = setInterval(function () {
  let sheet;
  let rules;
  let cssLength;
  let cssNum = 0;
  let length = document.styleSheets.length;
  for (let i = 0; i < length; i++) {
    sheet = document.styleSheets[i];
    //链接不存在,跳过
    if(!sheet.href) {
      continue;
    }
    console.log(sheet)
    if(sheet) {
      console.log(‘sheet ================true‘)
    }
    try {
      //严重:对于跨域的CSS文件,在谷歌浏览器,无法获取cssRules,只能用其它办法
      //已放弃该方式
      rules = sheet.rules ? sheet.rules : sheet.cssRules;
      cssLength = rules.length;
    } catch (e) {
      //如果CSS加载失败,例如链接打不开,会抛出异常,所以,需要捕获
      cssLength = 0;
    }
    //如果CSS加载成功,就记录下来
    if (cssLength > 0) {
      console.log(‘cssLength > 0 ================‘)
      for (let index in data) {
        if (data[index].default === sheet.href || data[index].backup === sheet.href) {
          cssNum ++;
          console.log(‘index =‘ + index)
          console.log(‘cssNum =‘ + cssNum)
          break;
        }
      }
    }
  }

  if(cssNum == data.length) {
    clearInterval(timer);
  }

}, 10000);
}

cssLoad(cssCDN);

vue项目性能优化6,优化:H5图片缓存,公开图片缓存工具
基于webpack 3、vue 2和vue-cli 2的性能优化。

如果图片还没缓存,先把图片下载,转换成base64字符串,保存到本地。
如果图片已经缓存,直接拿出来,显示到UI上,不用再请求网络。
如果图片改变,重新下载,更新本地缓存。
有两种方案选择:
1、web storage存储。window.localStorage大小限制是5MB左右。
2、IndexedDB存储。没有大小限制,或者说存储的大小更加大。类似SQLite数据库,参考IndexedDBUtils.js文件。推荐使用。

附带IndexedDBUtils工具类:
/**

IndexedDB存储
主要解决图片缓存。
官网资料:https://www.w3.org/TR/IndexedDB/
*/
export default {
/**

创建默认数据库

@returns {*|Promise}
/
createDefaultDB() {
return this.createOrUpdateDB(‘H5’, 1);
},
/*

创建或升级数据库

该方法的回调,可以创建表。

@param dbName 数据库

@param version 版本号

@returns {Promise}
/
createOrUpdateDB(dbName, version) {
return new Promise((resolve, reject) => {
let db;
let request = indexedDB.open(dbName, version);
//如果数据库不存在,会调用onupgradeneeded和onsuccess。
//如果数据库升级版本,也会调用onupgradeneeded和onsuccess。
request.onupgradeneeded = function () {
db = request.result;
/*
* 注意,只有onupgradeneeded可以调用db.createObjectStore()方法。
* 在onsuccess方法调用db.createObjectStore()方法会报错。
*/
//一个Promise执行2次resolve(),那么第二次,是不会执行的。因此,需要拆开成2个方法。
resolve(db);
};

/request.onsuccess = function (e) {
//db = request.result;
db = e.target.result;
let data = {db: db, createObjectStore: false};
resolve(data);
};/

request.onerror = function (e) {
reject(e);
};
});
},
//打开默认数据库
openDefaultDB() {
return this.openDB(‘H5’, 1);
},
/**

打开数据库

该方法的回调,不能创建表。会报错。

@param dbName 数据库名称

@param version 版本号(数字)

@returns {Promise}
*/
openDB(dbName, version) {
return new Promise((resolve, reject) => {
let db;
let request = indexedDB.open(dbName, version);

request.onsuccess = function (e) {
//db = request.result;
db = e.target.result;
resolve(db);
};

request.onerror = function (e) {
reject(e);
};
});
},
//判断默认表名是否存在
existDefaultTableName(db) {
//缓存图片
return this.existTableName(db, ‘CacheImage’);
},
//判断表名是否存在
existTableName(db, tableName) {
return db.objectStoreNames.contains(tableName);
},
//创建默认的表
createDefaultTable(db) {
let exist = this.existDefaultTableName(db);
//不存在,就创建
if(!exist) {
let objectStore = db.createObjectStore(‘CacheImage’, {
keyPath: ‘id’,
autoIncrement: true
});
objectStore.createIndex(‘code_index’, ‘code’, {unique: true});
}
return true;
},
saveCacheImage(db, data) {
return this.saveOrUpdate(db, ‘CacheImage’, data);
},
/**

新增或更新数据

如果数据不存在,就新增。

如果数据已存在,则更新。

如果是更新数据,那么,该条数据必须指定主键的值,否则报错。

@param tableName 表名

@param data json对象的数据,例如:{name: ‘test’, age: 10}
/
saveOrUpdate(db, tableName, data) {
return new Promise((resolve, reject) => {
let tx = db.transaction(tableName, ‘readwrite’);
let store = tx.objectStore(tableName);
let request = store.put(data);
request.onsuccess = function () {
resolve();
};
request.onerror = function (e) {
reject(e);
};
});
},
findCacheImageByIndex(db, code) {
return this.findByIndex(db, ‘CacheImage’, ‘code_index’, code);
},
/*

通过唯一索引,查询数据

@param db 数据库

@param tableName 表名

@param indexName 唯一索引(该字段的数据不会重复)

@param searchContent 搜索内容

@returns {Promise}
/
findByIndex(db, tableName, indexName, searchContent) {
return new Promise((resolve, reject) => {
let tx = db.transaction(tableName, ‘readonly’);
let store = tx.objectStore(tableName);
let index = store.index(indexName);
let request = index.get(searchContent);
request.onsuccess = function () {
let data = request.result;
if(data !== undefined) {
resolve(data);
} else {
resolve(null);
}
};
});
},
//异步加载图片,转换成base64字符串
loadImage(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(‘GET’, url, true);
//超时时间,单位是毫秒
xhr.timeout = 60000;
xhr.responseType = ‘blob’;
xhr.ontimeout = (e) => {
reject(e);
};
xhr.onload = () => {
if(xhr.status === 200) {
let blob = xhr.response;
let fileReader = new FileReader();
//转换成base64
fileReader.onload = (e) => {
let base64 = e.target.result;
resolve(base64);
};
fileReader.readAsDataURL(blob);
}
};
xhr.send();
});
},
/*

异步加载图片,并缓存起来

@param code 编码

@param imageUrl 图片链接

@returns {Promise} 图片的base64字符串,用于显示在元素的src上
*/
loadCacheImage(code, imageUrl) {
return new Promise((resolve, reject) => {
this.createDefaultDB().then((db) => {
this.createDefaultTable(db);
});

this.openDefaultDB().then((db) => {
this.findCacheImageByIndex(db, code).then((data) => {
if(!data) {
this.loadImage(imageUrl).then((base64) => {
resolve(base64);
this.saveCacheImage(db, {
code: code,
url: imageUrl,
base64: base64});
});
} else {
//链接改变,重新下载图片
if(data.url !== imageUrl) {
this.loadImage(imageUrl).then((base64) => {
resolve(base64);
this.saveCacheImage(db, {
id: data.id,
code: code,
url: imageUrl,
base64: base64});
});
} else {
resolve(data.base64);
}
}
});
});
});
}
};

 

以上是关于vue项目性能优化系列的主要内容,如果未能解决你的问题,请参考以下文章

前端性能优化,压缩包体积提升打包速度

Vue 项目性能优化

浅谈Vue 项目性能优化 经验

vue项目性能优化

vue系列之项目优化

Webpack 性能优化系列 - oneOf