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.2” 2、打开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项目性能优化系列的主要内容,如果未能解决你的问题,请参考以下文章