说在最前面的
因为最近在来到了新的公司,在写新的项目,前端技术栈是vue,就打起了vue的性能优化的主意。
本文因为都是自己个人的一些总结的原因,所以有些地方可能难免会有些不足,还望各位大佬可以指出,我好作更改~
本文将会长期更新~
图片优化
更改下思路,现在认为图片的优化在一个项目起到的作用是最大的,例如将一个原本有着1M或者五六百k的图片优化一下,包括但不限于使用CDN、压缩文件等。 这部分的优化不局限于在vue中使用,几乎在所有的前端项目中都是通用的~
打包优化
路由懒加载
{
path: '/home',
name: 'home',
component: import(/* webpackChunkName: '你要设置的chunk的name' */ '../page/home')
}
组件懒加载
这里的 /* webpackChunkName: "[request]" */ 中的 request 表示实际解析的文件名。
const Tabs = () => import(/* webpackChunkName: "[request]" */'_c/Tabs')
export default {
components: {
IconList
}
}
第三方CDN
2020-10-3
首先,为什么我们要明确我们为什么要使用externals~
我们在进行开发的时候,我们有可能会使用splitChunks来进行代码的分割,但是对于一些存在着官方CDN的外部库,我们还是更期望使用CDN。
webpack已经为我们提供了externals属性,一些平时基本不会改变的基础库我们可以将它们独立出来单独加载。如此,我们可以就能利于浏览器的缓存机制,不用每次都重新加载这些库文件。
我推荐如下写法:
//vue.config.js
// chainWebpack中,判断是生产环境时
config.externals({ //这里需要注意,chainWebpack是链式调用,所以这边是一个函数,不然我们的包不会被排除
vue: 'Vue',
'element-ui': 'ELEMENT',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
'echarts': 'echarts'
})
const cdn = {
css: ['//unpkg.com/element-ui@2.13.2/lib/theme-chalk/index.css'],
js: [
'//unpkg.com/vue@2.6.10/dist/vue.min.js',
'//unpkg.com/vue-router@3.0.2/dist/vue-router.min.js',
'//unpkg.com/vuex@3.1.0/dist/vuex.min.js',
'//unpkg.com/axios@0.18.1/dist/axios.min.js',
'//unpkg.com/element-ui@2.13.2/lib/index.js',
'https://cdn.bootcdn.net/ajax/libs/echarts/4.2.1/echarts.min.js'
]
}
config.plugin('html').tap(args => {
args[0].cdn = cdn
return args
})
到这里之后,我们不要忘记在index.html中添加cdn的引用~
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.js) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
当然了,如果你不喜欢这种写法,我们还以使用一种更加朴素的写法~
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
在这里的CDN,我是更加推荐使用官方的CND,这样相对来说更稳定一些~ 当然了,这只是我个人的一点建议~
**TIPS: ** 对于静态资源,对于字体文件,推荐使用CDN,或者如果你的产品其中的文字基本都是一些固定一变的,可以学习一下苹果官网对于字体的处理方式~
splitChunk
2020-10-3
对于splitChunk,目前已经可以搜索到很多的使用方式了
首先,虽然我们都说它的最重要的配置项是cacheGroups,但是其它的我们也需要去了解,有的时候如果不解的话,就有可能会踩坑
- chunks:表示从哪些chunks里面抽取代码,除了三个可选字符串值 initial、async、all 之外,还可以通过函数来过滤所需的 chunks;
- minSize:表示抽取出来的文件在压缩前的最小大小,默认为 30000;
- maxSize:表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
- minChunks:表示被引用次数,默认为1;
- maxAsyncRequests:最大的按需(异步)加载次数,默认为 5;
- maxInitialRequests:最大的初始化加载次数,默认为 3;
- automaticNameDelimiter:抽取出来的文件的自动生成名字的分割符,默认为 ~;
- name:抽取出来文件的名字,默认为 true,表示自动生成文件名;
- cacheGroups: 缓存组 说一个我自己踩过的坑,可以看看我下面的这段代码:
//vue.config.js => chainWebpack(config)
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
vexTable: {
name: 'chunk-vexTable',
priority: 30,
test: /[\\/]node_modules[\\/]_?vxe-table(.*)/
},
moment: {
name: 'chunk-moment',
priority: 30,
test: /[\\/]node_modules[\\/]_?moment(.*)/
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 1,
priority: 5,
reuseExistingChunk: true
}
}
})
当燃了,我之前没有看完api,所以我理所当然的认为,我这样做是完全可行的,但是当我打包之后我发现永远只能生成三个chunk
那么,对于这个问题,需要怎么解决呢?
我自己的解决方法是这样的:
- Element使用CDN,echarts按需加载
- maxAsyncRequests 和 maxInitialRequests 两个配置项调整了一下
Image-webpack-loader
Image-webpack-loader 可以在打包的时候对图片进行优化
chainWebpack(config) {
/** 忽略其它配置 ***/
config
.when(process.env.NODE_ENV !== 'development',
/** 忽略其它配置 ***/
config => {
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.9], speed: 4 },
gifsicle: { interlaced: false }
// webp: { quality: 75 }
})
/** 忽略其它配置 ***/
})
}
这边,我写在这个的时候遇到了如下问题:
- image-webpack-plugin无法安装的问题
解决方案:这边使用cnpm进行安装
2. 在打完压缩完之后,误以为图无用,需要注意,这边Png的图片,可能会用默认的预览无法查看,但是在浏览器上应该是可以查看的。 2. libpng的问题,因为这是mac电脑,我用homebrew安装过了,不清楚win中的问题
ScriptExtHtmlWebpackPlugin
这个插件是会将我拉runtimechunck之后的runtimeChunk.xx.js这样的文件内联至我们的Index.js文件之中。因为这样的一个文件是经常改变,如果不作处理的话,我们每次都需要重新请求它,如果将之进行内联的话,可以大大节省我们的时间。
配置代码如下:
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
inline: /runtime\..*\.js$/
}])
.end()
这段代码也是在生产打包的时候才应该执行的,所以我将之放在 chainWebpack 中,这里有一点需要说明的是它最好在 splitChunk 之前,同时如果你使用了 html-webpack-plugin 的话,那么它也必须要是在 html-webpack-plugin 之后,注意是必须!
terser-webpack-plugin
在这里,我最初的选择是 uglifyjs-webpack-plugin ,这里之所以不使用它,因为它不支持es6的语法,总是会报一些奇奇怪怪的错误。然后我选选择安装和使用 terser-webpack-plugin
注意,这里里我发现了一个问题,通过查阅vue-cli4的升级变化,我发现了vue-cli4已经为我们集成了这个插件,我们只需要在webpckChain里对它进行操作就行了,具体的代码使用如下:
config.optimization.minimizer('terser').tap((args) => {
// 去除生产环境console
args[0].terserOptions.compress.drop_console = true
return args
})
gzip
对于gzip这个并没有什么好说的,我在这里选择使用的是 compression-webpack-plugin ,以下是我的具体配置
config.plugin('CompressionWebpackPlugin')
.use('compression-webpack-plugin', [{
filename(asset) { asset = '[file].gz[query]'; return asset },
algorithm: 'gzip',
test: productionGzipExtensions,
threshold: 10240, // 只有大小大于该值的资源会被处理 10240
minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
deleteOriginalAssets: false // 删除原文件
}])
可能在写法上会和你在网上看到的有所不同,但是相信我,它们的效果应该是一样的。
热更新失效
对于偶然的热更新失效的问题,这里网上也有提供许多的方法,有一些是通过安装webpack-dev-server来解决这样的问题,这里,我推荐使用如下方法:
config.resolve.symlinks(true);
代码优化
前文在对于vue-cli4的一优化已经讲了许多,接下来我想说说关于代码层面的优化。
作为一个程序员,我们应该享受 code review 的过程~
v-if 与 v-show
首先明确二者的区别
- v-if 如果为false,则初始不会渲染,如果切换v-if的值,它总是会重新渲染
- v-show 不管初始值为何,都会渲染,且切换值仅仅是切换display:none这一css
在一些特定的场景下,如果某一个组件会被频繁的显示与隐藏,那么这里更推荐使用 v-show
那么,如果我们的这个组件每次我们都想要将之重新渲染,那么我们可以在这里使用v-if来触发它的强制重新渲染
在这里,如果你频繁地需要通过v-if来进行强制重新渲染的话,那么这是vue官方不建议的
forceUpdate
使用这个方法的时候,我们需要注意的是,这个方法不会更新我们的计算属性,它仅仅会强制渲染我们的视图
我们需要这么做:
//component.vue
export default {
methods:{
componentUpdate(){
this.$forceUpdate()
}
}
}
key的方式
我们可以这么使用
//component.vue
<template>
<component-child :key='componentChildKey'></component-child>
</template>
<script>
export default {
data(){
return {
componentChildKey:1
}
},
methods:{
upDateComponentChild(){
this.componentChildKey += 1
}
}
}
</script>
善用template
对于一些不必要的外层元素,我们总是应该尽可能地使用template来替代它
v-for 与 v-if 与 key
首先,我们必须要明确,v-for与v-if不能是同级的,同时,我们使用了v-for应该为循环荐加上一个key,以提高diff的运行效率。
同时,如果我们的v-for 放在template上,那么,我们的:key不能放在template上,应该放在被包裹的最外层元素上。
而至于,为什么会提高diff的效率,将会在以后补充~
v-once
对于仅会渲染一次的元素,我们应该使用v-once
这里需要注意,如果我们在v-for中使用了v-once,那么我们必须要为这个使用的元素加上一个key
它的原理,将会在以后进行补充~
数据过滤的必要
Vue在初始化的时候会对数据进行劫持,为数据对象的每个属性添加依赖
这里,我推荐使用map映射的方式来解决这个问题,即是在获取的时候就通过map将数据“洗”一遍,剔除一些我们并不需要的数据项。
同样地数据处理问题
对于完全不需要进行响应式变动的数据,我们可以使用Object.freeze()来将之进行冻结,对于configurable属性为false的数据,vue在劫持的时候会跳过~
按需引入
类似element的需要引入这种
v-lazyload的使用
这是一个vue的懒加载的使用
对于异步请求
这里,我们如果使用了异步请求接到了一个Promise数据,我推荐使用async await的方法,并且最好是使用try catch进行包裹
对于在actions中发起请求,我们建议是如果这里不会触发state或者mutations的操作,就尽量不要将请求放到actions中。
对于watch computed 以及metheds的合理使用
- watch
监听某一个数据,它会影响到多个数据或者当它变化时,我们需要进行大量的其它操作
- computed
它是依赖于其它数据进行变化的,亦或者此项数据的计算有着极大地性能消耗
- methods
它是实时更新的
同时监听多个值
如果一个数据的变化,同时依赖于多个数据,我们最好是使用以下这种方法
<script>
export default(){
computed:{
handelChange(){
const {fileName,FolderName} = this.$props
}
},
watch:{
handelChange:{
handler:function(val,oldval){
//do somting
},
immediate:true
}
}
}
</script>
对于图片
我个人建议对于大图片使用Picture标签的source
其它优化
SEO优化
预渲染
后续完善
SSR
后续完善