Vue项目优化小结
最近花了点时间优化了一下这几个月开发的项目,在网上搜集了很多并实践,如有不对欢迎指出;如有更好的优化方法,请告诉我!谢谢!
调试工具network中的几个参数
DOMContentLoaded、Finish和Load的含义
-
DOMContentLoaded: DOM树构建完成;
-
Load:页面加载完毕,包括DOM树构建和请求css、图片等外部资源;
-
Finish:页面上所有http请求发送到响应完成的时间。
HTTP1.0/1.1 协议限定,单个域名的请求并发量是 6 个,即 Finish 是所有请求(不只是XHR请求,还包括DOC,img,js,css等资源的请求)在并发量为6的限制下完成的时间
Load > DOMContentLoaded意味着请求外部资源时间较大;
Finish > Load意味着页面请求量(和时间)较大(较长)。
Waterfall参数解析
颜色解析
- 浅灰:查询中;
- 深灰:停滞,代理转发,请求发送;
- 橙色:初始连接;
- 绿色:等待中;
- 蓝色:内容下载。
优化方向
- 减少瀑布宽度,即减少资源的加载时间;
- 减少瀑布的高度,即减少请求的数量;
- 将绿色的”开始渲染“线向左移动,即通过优化资源请求顺序来加快渲染时间。
Vue项目优化实践
脚手架是v-cli3版本的。
前期准备
生成打包报告
npm run build --report
可打开打包后的report.html
查看打包体积,根据体积进行优化。
使用PageSpeed Insights
插件
可以对网址进行评分,还会给出一些建议。
接下来分几种方向进行优化。
修改vue.config.js,优化打包体积
路由懒加载
不使用路由懒加载的话,会在一开始就下载完所有路由对应的组件文件。
// 修改前
import xxx from '@/components/xxx';
routes:[
path: 'xxx',
name: 'xxx',
component: xxx
]
// 修改后
routes:[
path: 'xxx',
name: 'xxx',
component: () => import(/* webpackChunkName: "Chat" */ './components/xxx')
]
关闭预加载模块
虽然在上面我们配置了异步加载,但是因为 vuecli 3
默认开启 prefetch
(预先加载模块),提前获取用户未来可能会访问的内容,在首屏会把这十几个路由文件,都一口气下载了,所以我们要关闭这个功能。
// vue.config.js
module.exports = {
...
chainWebpack: (config) => {
// 移除 prefetch 插件
config.plugins.delete('prefetch-index')
// 移除 preload 插件
config.plugins.delete('preload-index');
},
...
}
Preload 是一个新的控制特定资源如何被加载的新的 Web 标准,这是已经在 2016 年 1 月废弃的 subresource prefetch 的升级版。这个指令可以在 中使用,比如 。一般来说,最好使用 preload 来加载你最重要的资源,比如图像,CSS,JavaScript 和字体文件。这不要与浏览器预加载混淆,浏览器预加载只预先加载在HTML中声明的资源。preload 指令事实上克服了这个限制并且允许预加载在 CSS 和JavaScript 中定义的资源,并允许决定何时应用每个资源。
Prefetch 是一个低优先级的资源提示,允许浏览器在后台(空闲时)获取将来可能用得到的资源,并且将他们存储在浏览器的缓存中。一旦一个页面加载完毕就会开始下载其他的资源,然后当用户点击了一个带有 prefetched 的连接,它将可以立刻从缓存中加载内容。有三种不同的 prefetch 的类型,link,DNS 和 prerendering。
按需加载
项目中使用vant
框架,加载依赖包时应该这么操作:
// 优化前
import vantUI from 'vant'
Vue.use(vantUI)
// 优化后
import { Button, Toast, Picker, List, Cell, PullRefresh, Rate, Popup, Loading, DatetimePicker, ActionSheet } from 'vant'
import 'vant/lib/index.css';
Vue.use(Button);
Vue.use(Picker);
...
gzip压缩
nmp install compression-webpack-plugin -D
// vue.config.js
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = {
...
configureWebpack: () => {
let plugins = [];
...
if (process.env.NODE_ENV === "'production'"){
plugins.push(new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets:false//是否删除源文件
}))
}
return {
output: { // 输出重构 打包编译后的 文件名称 【模块名称.版本号.时间戳】
filename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`,
chunkFilename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`
},
plugins
}
},
...
}
打包之后文件被压缩了~不过需要告诉服务器做相应的配置,如果发送请求的浏览器支持 gzip
,就发送给它 gzip
格式的文件。
CSS拆分
CSS是否要拆分就看具体环境了,拆分了之后请求的资源数会变多,不拆分请求的体积可能会很大。
module.exports = {
...
// css相关配置
css: {
// 是否使用css分离插件 ExtractTextPlugin,不需要改成false即可
extract: true,
// 开启 CSS source maps?
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
modules: false
},
...
}
CDN
是否使用CDN也看具体环境了,我用了CDN请求资源数更多了,首屏加载又变慢了°(°ˊДˋ°) °。
<!-- index.html -->
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
// vue.config.js
module.exports = {
...
configureWebpack: () => {
...
externals:{
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios'
}
}
...
}
其他
在我的打包报告里面,lodash
的体积占比很大,整个项目中,只有三处地方使用了lodash
的throttle
方法。
// 优化前
import * as _ from 'lodash'
private togglePicker = _.throttle(this.togglePickerAction, 400);
// 优化后
import * as _ from 'lodash/throttle'
private togglePicker = _(this.togglePickerAction, 400);
这样打包的时候只会打包有关lodash
的throttle
的东西,再次打包会发现lodash
中的体积已经大大减少。
补充
const path = require('path')
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ['js', 'css']
const resolve = (dir) => path.join(__dirname, dir)
const Timestamp = new Date().getTime();
module.exports = {
productionSourceMap: false, // 是否在构建生产包时生成 sourceMap 文件,false将提高构建速度
outputDir: `release/${process.env.VUE_APP_Version}`,
lintOnSave: false,
publicPath: process.env.NODE_ENV === 'production' ? '/online/' : './',
chainWebpack: (config) => {
config.module.rule('pug')
.test(/\.pug$/)
.use('pug-html-loader')
.loader('pug-html-loader')
.end()
config.resolve.alias
.set('@', resolve('src'))
.end()
// 移除 prefetch 插件
config.plugins.delete('prefetch-index')
// 移除 preload 插件
config.plugins.delete('preload-index');
},
// lintOnSave: true,
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [
path.resolve(__dirname, 'src/assets/style/common.less')
]
}
},
// css相关配置
css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: true,
// 开启 CSS source maps?
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
modules: false
},
configureWebpack: () => {
let plugins = [];
plugins.push(new SpeedMeasurePlugin());
if (process.env.NODE_ENV === "'production'"){
plugins.push(new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets:false//是否删除源文件
}))
}
return {
output: { // 输出重构 打包编译后的 文件名称 【模块名称.版本号.时间戳】
filename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`,
chunkFilename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`
},
plugins
}
},
pages: {
index: {
// page 的入口
entry: 'src/main.ts',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
}
},
parallel: require('os').cpus().length > 1,
devServer: {
proxy: {}
}
}
运行优化
合理使用v-if和v-show
v-for加上key
使用Object.freeze优化长列表
初始化时,vue会对data做getter、setter改造,在现代浏览器里,这个过程实际上挺快的,但仍然有优化空间。
Object.freeze()
可以冻结一个对象,冻结之后不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。该方法返回被冻结的对象。当你把一个普通的 JavaScript 对象传给 Vue 实例的
data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。但 Vue 在遇到像
Object.freeze()
这样被设置为不可配置之后的对象属性时,不会为对象加上 setter getter 等数据劫持的方法作者:Mr_Google
链接:https://juejin.im/post/5d5e89aee51d453bdb1d9b61
本项目里面有很多纯展示列表,所以修改如下:
this.list = Object.freeze(this.list.concat(list));
监听事件、eventBus和计时器等即时销毁
activated () {
document.addEventListener('click', this.handleClick)
},
deactivated () {
document.removeEventListener('click', this.handleClick)
},
someMethod(){
eventBus.$on(this.action);
}
beforeDestroy(){
eventBus.$off(this.action);
}
编译优化
项目开发时候热更新打包好慢…修改了.env.dev,在里面加入这么一句:
VUE_CLI_BABEL_TRANSPILE_MODULES:true
其他优化
合并CSS,精简JS,优化图片等。
首次发布于:Vue项目优化小结
上一篇: Oracle的substr函数的用法
下一篇: 十三、Java集合框架之Map集合