SPA单页面应用优化VUE性能优化
日期:2018-07-29
来源:程序思维浏览:2786次
Spa单页面应用比如vue、react、angular都是属于单页面应用,那么如何优化呢?咱们拿vue举例。

单页面应用离不开构建工具比如webpack、fis3、gulp等,目前应用最多的就是webpack,咱们就用webpack来做优化,webpack能做哪些优化呢?
一、 CSS和JS压缩合并
由于webpack本身集成了UglifyJS插件(webpack.optimize.UglifyJsPlugin)来完成对JS与CSS的压缩混淆,无需引用额外的插件,
其命令 webpack -p 即表示调用UglifyJS来压缩代码,还有不少webpack插件如 html-webpack-plugin 也会默认使用UglifyJS。
uglify-js的发行版本只支持ES5,如果你要压缩ES6+代码请使用开发分支。
UglifyJS可用的选项有:
//使用插件html-webpack-plugin打包合并html
//使用插件extract-text-webpack-plugin打包独立的css
//使用UglifyJsPlugin压缩代码
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require("webpack");
module.exports = {
entry: {
bundle : './src/js/main.js'
},
output: {
filename: "[name]-[hash].js",
path: dirname + '/dist'
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'url-loader?limit=8192'
}
]
},
resolve:{
extensions:['.js','.css','.json'] //用于配置程序可以自行补全哪些文件后缀
},
plugins:[
new HtmlWebpackPlugin({
title: 'hello webpack',
template:'src/component/index.html',
inject:'body',
minify:{ //压缩HTML文件
removeComments:true, //移除HTML中的注释
collapseWhitespace:true //删除空白符与换行符
}
}),
new ExtractTextPlugin("[name].[hash].css"),
new webpack.optimize.UglifyJsPlugin({
compress: { //压缩代码
dead_code: true, //移除没被引用的代码
warnings: false, //当删除没有用处的代码时,显示警告
loops: true //当do、while 、 for循环的判断条件可以确定是,对其进行优化
},
except: ['$super', '$', 'exports', 'require'] //混淆,并排除关键字
})
]
};
这里需要注意的是压缩的时候需要排除一些关键字,不能混淆,比如$或者require,如果混淆的话就会影响到代码的正常运行。
列几个压缩时常有的属性:
1. dead_code -- 移除没被引用的代码
2. loops -- 当 do 、 while 、 for 循环的判断条件可以确定是,对其进行优化。
3. warnings -- 当删除没有用处的代码时,显示警告
二、 代码分割按需加载(require.ensure)
require.ensure()
webpack 在编译时,会静态地解析代码中的 require.ensure(),同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 通过 jsonp 来按需加载。
语法如下:
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
依赖 dependencies
这是一个字符串数组,通过这个参数,在所有的回调函数的代码被执行前,我们可以将所有需要用到的模块进行声明。
回调 callback
当所有的依赖都加载完成后,webpack会执行这个回调函数。require 对象的一个实现会作为一个参数传递给这个回调函数。因此,我们可以进一步 require() 依赖和其它模块提供下一步的执行。
chunk名称 chunkName
chunkName 是提供给这个特定的 require.ensure() 的 chunk 的名称。通过提供 require.ensure() 不同执行点相同的名称,我们可以保证所有的依赖都会一起放进相同的 文件束(bundle)。
让我们来看以下的项目
\\ file structure
|
js --|
| |-- entry.js
| |-- a.js
| |-- b.js
webpack.config.js
|
dist
\\ entry.js
require('a');
require.ensure([], function(require){
require('b');
});
\\ a.js
console.log('***** I AM a *****');
\\ b.js
console.log('***** I AM b *****');
\\ webpack.config.js
var path = require('path');
module.exports = function(env) {
return {
entry: './js/entry.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
}
通过执行这个项目的 webpack 构建,我们发现 webpack 创建了2个新的文件束, bundle.js 和 0.bundle.js。
entry.js 和 a.js 被打包进 bundle.js.
b.js 被打包进 0.bundle.js.
require.ensure 内部依赖于 Promises。 如果你在旧的浏览器中使用 require.ensure 请记得 去 shim Promise. es6-promise polyfill.
require.ensure() 的坑点
空数组作为参数
require.ensure([], function(require){
require('./a.js');
});
以上代码保证了拆分点被创建,而且 a.js 被 webpack 分开打包。
依赖作为参数
require.ensure(['./a.js'], function(require) {
require('./b.js');
});
上面代码, a.js 和 b.js 都被打包到一起,而且从主文件束中拆分出来。但只有 b.js 的内容被执行。a.js 的内容仅仅是可被使用,但并没有被输出。
想去执行 a.js,我们需要异步地引用它,如 require('./a.js'),让它的 JavaScritp 被执行。
那么require.ensure如何在vue中使用呢?
require.ensure主要是在vue的路由中使用,如果不使用代码分割随着页面的增多build之后app.727c5a981063de8a5ac8.js会越来越大。使用之后会将这个js文件按页面数量分割从而做到页面按需加载js。废话少说看代码:
在router/index.js里面:
比如首页路由:
const INDEX = r => require.ensure([], () => r(require('../components/home/index/index')), 'index')
export default [
{
path: '/',
component: INDEX,
meta: { title: '首页'}
}
]
好啦,vue中的代码分割就是这么使用简单吧!
然后咱们在看代码中如何优化?
一、图片懒加载
为什么要使用图片懒加载?
首先我们来了解一下懒加载是为了解决什么问题。当我们在项目中遇到一些比较大的场景,里面包含着数以百计的图片,当用户访问这些页面的时候,用户等待的时间特别长,这样的话,就有可能导致一部分的用户没有耐心,而丢失这部分用户,于是就有了这里的图片懒加载。在用户刚进入场景的时候,先加载几页的图片,开始显示给用户,然后在用户每翻一页时,再相应的加载后面对应的一页中的图片,这样,如果用户看了几页不想看了,后边的图片就不用加载了,减轻了服务器的压力。而相应的,用户在进入场景时,等待的时间也减少了许多。
懒加载在一些大型的网站中见到的比较多,因为网站考虑到性能、流量及用户体验方面的问题,在用户点击开网站的首页的时候,网站想尽可能的显示更多的信息给用户,又要考虑到服务器的性能的问题,还不能让用户等待的时间过长,所以这里就会使用图片的懒加载。图片的懒加载可以让用户按照需求从服务器上加载图片,这样即节省了用户在代开首页时的等待时间,也节省了服务器的流量,所以是一个好的选择。
使用vue-lazyload做图片懒加载减少请求数。
1. 安装插件:
npm install vue-lazyload --save-dev
2. main.js引入插件:
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad,{
error:'./static/error.png',
loading:'./static/loading.png'
})
3. vue文件中将需要懒加载的图片绑定 v-bind:src 修改为 v-lazy
<img class="item-pic" v-lazy="newItem.picUrl"/>
二、针对第三方库文件使用cdn方式
之前我是使用npm安装之后import方式,这样就相当于一起打包到了vendor文件里了。
按照bootstrap官网引入相应的cdn文件后(包括jquery和bootstrap),我的项目最大文件大小变成了680kb,加载速度从8.5s变成了5.8s。
然后试图引入element-ui之后,最大文件大小没多大变化,但是加载速度从5.8s变成了4.2s。
接着又引入了vue和element-ui cdn文件,这下网站有了巨大突破,最大文件竟然降到了37kb,自己都被惊讶到了,加载速度也从4.2s变成了80ms,整个网站加载完成只花了1.5s,终于达到了秒开的效果。
如何使用cdn方式呢?
1、 提取js/css到cdn
以一个vue项目为例。项目引用了vue、axios、iview、iview的css:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios'
import iView from 'iview'
import 'iview/dist/styles/iview.css'
2、 提取js到外部,减小vendor.js体积
1. 在/build/webpack.base.conf.js中,增加externals:
module.exports = {
externals: {
'vue': 'Vue',
'axios': 'axios',
'iview': 'iview'
}
}
注意,externals的键值对中,键应为在/src/main.js中import的名称,值为引用的外部文件export的名称。以cdn.bootcss.com的库文件为例,vue的导出名为Vue,mint-ui为MINT,vue-router为VueRouter。
3、复制/index.html为/index.dev.html,并修改/build/webpack.dev.conf如下:
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
// template: 'index.html',
template: 'index.dev.html',
inject: true
})
]
* 这是为了解决dev环境下,重复引用库的问题。
4、在/index.html中,引入cdn文件
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script>
<script src="https://cdn.bootcss.com/iview/2.6.0/iview.min.js"></script>
</body>
可见,处理前vender.js大小148KB,处理后大小13.7KB。
另增加3个CDN文件,大小34.9KB、5.6KB、115KB,增加115.5KB。
总体积变化不大。将大体积文件放置在cdn上,减小了服务器流量压力,加快了不同地区网页加载速度。
5、提取css到外部,减小app.css体积
在/src/main.js中,前端框架的CSS引用,都可去除,并改为在/index.html中引用其CDN版本。
1. 去除/src/main.js中的CSS文件import,改为在开发环境下require
// import 'iview/dist/styles/iview.css
if (process.env.NODE_ENV === 'development') {
require('mint-ui/lib/style.css')
}
6、在/index.html中,引入cdn文件
<head>
<link href="https://cdn.bootcss.com/iview/2.6.0/styles/iview.css" rel="stylesheet">
<title>app</title>
</head>
三、使用第三方框架和插件按需加载
对于项目中用到的一些UI框架,比如 Element UI 、Mint UI、Cube-UI 等等。如果我们只使用框架的部分组件,那么可以不要引入整个框架,按需引入用到的组件。
以Mint UI为例:
// 引入全部组件
import Mint from 'mint-ui';
import 'mint-ui/lib/style.css'
Vue.use(Mint);
// 按需引入部分组件
import { CellSwipe } from 'mint-ui';
Vue.component(CellSwipe.name, CellSwipe);
好了spa单页面应用的优化就写到这里,希望对大家有帮助。

单页面应用离不开构建工具比如webpack、fis3、gulp等,目前应用最多的就是webpack,咱们就用webpack来做优化,webpack能做哪些优化呢?
一、 CSS和JS压缩合并
由于webpack本身集成了UglifyJS插件(webpack.optimize.UglifyJsPlugin)来完成对JS与CSS的压缩混淆,无需引用额外的插件,
其命令 webpack -p 即表示调用UglifyJS来压缩代码,还有不少webpack插件如 html-webpack-plugin 也会默认使用UglifyJS。
uglify-js的发行版本只支持ES5,如果你要压缩ES6+代码请使用开发分支。
UglifyJS可用的选项有:
- parse 解释
- compress 压缩
- mangle 混淆
- beautify 美化
- minify 最小化//在插件HtmlWebpackPlugin中使用
- CLI 命令行工具
- sourcemap 编译后代码对源码的映射,用于网页调试
- AST 抽象语法树
- name 名字,包括变量名、函数名、属性名
- toplevel 顶层作用域
- unreachable 不可达代码
- option 选项
- STDIN 标准输入,指在命令行中直接输入
- STDOUT 标准输出
- STDERR 标准错误输出
- side effects函数副作用,即函数除了返回外还产生别的作用,比如改了全局变量
//使用插件html-webpack-plugin打包合并html
//使用插件extract-text-webpack-plugin打包独立的css
//使用UglifyJsPlugin压缩代码
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require("webpack");
module.exports = {
entry: {
bundle : './src/js/main.js'
},
output: {
filename: "[name]-[hash].js",
path: dirname + '/dist'
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'url-loader?limit=8192'
}
]
},
resolve:{
extensions:['.js','.css','.json'] //用于配置程序可以自行补全哪些文件后缀
},
plugins:[
new HtmlWebpackPlugin({
title: 'hello webpack',
template:'src/component/index.html',
inject:'body',
minify:{ //压缩HTML文件
removeComments:true, //移除HTML中的注释
collapseWhitespace:true //删除空白符与换行符
}
}),
new ExtractTextPlugin("[name].[hash].css"),
new webpack.optimize.UglifyJsPlugin({
compress: { //压缩代码
dead_code: true, //移除没被引用的代码
warnings: false, //当删除没有用处的代码时,显示警告
loops: true //当do、while 、 for循环的判断条件可以确定是,对其进行优化
},
except: ['$super', '$', 'exports', 'require'] //混淆,并排除关键字
})
]
};
这里需要注意的是压缩的时候需要排除一些关键字,不能混淆,比如$或者require,如果混淆的话就会影响到代码的正常运行。
列几个压缩时常有的属性:
1. dead_code -- 移除没被引用的代码
2. loops -- 当 do 、 while 、 for 循环的判断条件可以确定是,对其进行优化。
3. warnings -- 当删除没有用处的代码时,显示警告
二、 代码分割按需加载(require.ensure)
require.ensure()
webpack 在编译时,会静态地解析代码中的 require.ensure(),同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 通过 jsonp 来按需加载。
语法如下:
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
依赖 dependencies
这是一个字符串数组,通过这个参数,在所有的回调函数的代码被执行前,我们可以将所有需要用到的模块进行声明。
回调 callback
当所有的依赖都加载完成后,webpack会执行这个回调函数。require 对象的一个实现会作为一个参数传递给这个回调函数。因此,我们可以进一步 require() 依赖和其它模块提供下一步的执行。
chunk名称 chunkName
chunkName 是提供给这个特定的 require.ensure() 的 chunk 的名称。通过提供 require.ensure() 不同执行点相同的名称,我们可以保证所有的依赖都会一起放进相同的 文件束(bundle)。
让我们来看以下的项目
\\ file structure
|
js --|
| |-- entry.js
| |-- a.js
| |-- b.js
webpack.config.js
|
dist
\\ entry.js
require('a');
require.ensure([], function(require){
require('b');
});
\\ a.js
console.log('***** I AM a *****');
\\ b.js
console.log('***** I AM b *****');
\\ webpack.config.js
var path = require('path');
module.exports = function(env) {
return {
entry: './js/entry.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
}
通过执行这个项目的 webpack 构建,我们发现 webpack 创建了2个新的文件束, bundle.js 和 0.bundle.js。
entry.js 和 a.js 被打包进 bundle.js.
b.js 被打包进 0.bundle.js.
require.ensure 内部依赖于 Promises。 如果你在旧的浏览器中使用 require.ensure 请记得 去 shim Promise. es6-promise polyfill.
require.ensure() 的坑点
空数组作为参数
require.ensure([], function(require){
require('./a.js');
});
以上代码保证了拆分点被创建,而且 a.js 被 webpack 分开打包。
依赖作为参数
require.ensure(['./a.js'], function(require) {
require('./b.js');
});
上面代码, a.js 和 b.js 都被打包到一起,而且从主文件束中拆分出来。但只有 b.js 的内容被执行。a.js 的内容仅仅是可被使用,但并没有被输出。
想去执行 a.js,我们需要异步地引用它,如 require('./a.js'),让它的 JavaScritp 被执行。
那么require.ensure如何在vue中使用呢?
require.ensure主要是在vue的路由中使用,如果不使用代码分割随着页面的增多build之后app.727c5a981063de8a5ac8.js会越来越大。使用之后会将这个js文件按页面数量分割从而做到页面按需加载js。废话少说看代码:
在router/index.js里面:
比如首页路由:
const INDEX = r => require.ensure([], () => r(require('../components/home/index/index')), 'index')
export default [
{
path: '/',
component: INDEX,
meta: { title: '首页'}
}
]
好啦,vue中的代码分割就是这么使用简单吧!
然后咱们在看代码中如何优化?
一、图片懒加载
为什么要使用图片懒加载?
首先我们来了解一下懒加载是为了解决什么问题。当我们在项目中遇到一些比较大的场景,里面包含着数以百计的图片,当用户访问这些页面的时候,用户等待的时间特别长,这样的话,就有可能导致一部分的用户没有耐心,而丢失这部分用户,于是就有了这里的图片懒加载。在用户刚进入场景的时候,先加载几页的图片,开始显示给用户,然后在用户每翻一页时,再相应的加载后面对应的一页中的图片,这样,如果用户看了几页不想看了,后边的图片就不用加载了,减轻了服务器的压力。而相应的,用户在进入场景时,等待的时间也减少了许多。
懒加载在一些大型的网站中见到的比较多,因为网站考虑到性能、流量及用户体验方面的问题,在用户点击开网站的首页的时候,网站想尽可能的显示更多的信息给用户,又要考虑到服务器的性能的问题,还不能让用户等待的时间过长,所以这里就会使用图片的懒加载。图片的懒加载可以让用户按照需求从服务器上加载图片,这样即节省了用户在代开首页时的等待时间,也节省了服务器的流量,所以是一个好的选择。
使用vue-lazyload做图片懒加载减少请求数。
1. 安装插件:
npm install vue-lazyload --save-dev
2. main.js引入插件:
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad,{
error:'./static/error.png',
loading:'./static/loading.png'
})
3. vue文件中将需要懒加载的图片绑定 v-bind:src 修改为 v-lazy
<img class="item-pic" v-lazy="newItem.picUrl"/>
二、针对第三方库文件使用cdn方式
之前我是使用npm安装之后import方式,这样就相当于一起打包到了vendor文件里了。
按照bootstrap官网引入相应的cdn文件后(包括jquery和bootstrap),我的项目最大文件大小变成了680kb,加载速度从8.5s变成了5.8s。
然后试图引入element-ui之后,最大文件大小没多大变化,但是加载速度从5.8s变成了4.2s。
接着又引入了vue和element-ui cdn文件,这下网站有了巨大突破,最大文件竟然降到了37kb,自己都被惊讶到了,加载速度也从4.2s变成了80ms,整个网站加载完成只花了1.5s,终于达到了秒开的效果。
如何使用cdn方式呢?
1、 提取js/css到cdn
以一个vue项目为例。项目引用了vue、axios、iview、iview的css:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios'
import iView from 'iview'
import 'iview/dist/styles/iview.css'
2、 提取js到外部,减小vendor.js体积
1. 在/build/webpack.base.conf.js中,增加externals:
module.exports = {
externals: {
'vue': 'Vue',
'axios': 'axios',
'iview': 'iview'
}
}
注意,externals的键值对中,键应为在/src/main.js中import的名称,值为引用的外部文件export的名称。以cdn.bootcss.com的库文件为例,vue的导出名为Vue,mint-ui为MINT,vue-router为VueRouter。
3、复制/index.html为/index.dev.html,并修改/build/webpack.dev.conf如下:
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
// template: 'index.html',
template: 'index.dev.html',
inject: true
})
]
* 这是为了解决dev环境下,重复引用库的问题。
4、在/index.html中,引入cdn文件
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script>
<script src="https://cdn.bootcss.com/iview/2.6.0/iview.min.js"></script>
</body>
可见,处理前vender.js大小148KB,处理后大小13.7KB。
另增加3个CDN文件,大小34.9KB、5.6KB、115KB,增加115.5KB。
总体积变化不大。将大体积文件放置在cdn上,减小了服务器流量压力,加快了不同地区网页加载速度。
5、提取css到外部,减小app.css体积
在/src/main.js中,前端框架的CSS引用,都可去除,并改为在/index.html中引用其CDN版本。
1. 去除/src/main.js中的CSS文件import,改为在开发环境下require
// import 'iview/dist/styles/iview.css
if (process.env.NODE_ENV === 'development') {
require('mint-ui/lib/style.css')
}
6、在/index.html中,引入cdn文件
<head>
<link href="https://cdn.bootcss.com/iview/2.6.0/styles/iview.css" rel="stylesheet">
<title>app</title>
</head>
三、使用第三方框架和插件按需加载
对于项目中用到的一些UI框架,比如 Element UI 、Mint UI、Cube-UI 等等。如果我们只使用框架的部分组件,那么可以不要引入整个框架,按需引入用到的组件。
以Mint UI为例:
// 引入全部组件
import Mint from 'mint-ui';
import 'mint-ui/lib/style.css'
Vue.use(Mint);
// 按需引入部分组件
import { CellSwipe } from 'mint-ui';
Vue.component(CellSwipe.name, CellSwipe);
好了spa单页面应用的优化就写到这里,希望对大家有帮助。
- 上一篇:面试当中get和post区别的总结
- 下一篇:vue双向绑定的原理
精品好课