内容简介:工作中一直在做一款公司内部的BI工具,将数据可视化的报表赋能给业务人员,报表配置者通过简单的拖拽操作即可生成报表。随着系统不断的完善,加上运维推广,我们积累了越来越多的用户。这时候用户体验的方方面面都体现出来了。我们也停下产品的功能迭代,将整个系统进行优化,旨在提升用户体验。以下是我对前端项目的优化总结。项目中在使用的之前也总结过一次
工作中一直在做一款公司内部的BI工具,将数据可视化的报表赋能给业务人员,报表配置者通过简单的拖拽操作即可生成报表。随着系统不断的完善,加上运维推广,我们积累了越来越多的用户。这时候用户体验的方方面面都体现出来了。我们也停下产品的功能迭代,将整个系统进行优化,旨在提升用户体验。以下是我对前端项目的优化总结。
Webpack 打包优化
项目中在使用的 Webpack 版本是3.x,本次优化的方案仍然是基于Webpack3.x版本的 Vue 脚手架进行优化。升级4.x在计划中。。。
之前也总结过一次 Webpack 2.x 在Vue2.x项目中的应用 ,提到过 Webpack 工程的一些优化方案,以下算是一个补充。
开启Gzip
尝试了下开启gzip,直接受益还是比较大的。下面是实际项目中打包结果。
-
Parsed的js,1.38M
-
Gizpped的js - 421.46K
通过数据分析,减少了**70.28%**的打包体积。
开启方式,在脚手架中修改配置文件: /config/index.js
// 生产模式
build: {
productionGzip: true // 开启Gzip压缩
}
复制代码
同时服务端 nginx 加入配置项
gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_comp_level 6; gzip_types application/javascript text/plain application/x-javascript text/css application/xml text/javascript application/json; gzip_vary on; 复制代码
重启 nginx 后刷新页面,在 Chrome develop tools 中 Network 查看网络链接 Request Headers 中出现 Accept-Encoding: gzip 即生效。
使用 Preload 插件
:bulb: 使用 Resource Hints 中的preload 与prefetch 来提升应用的性能。
关于 preload 与 prefetch
<link rel="preload"> 是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。
<link rel="prefetch"> 是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。
在 Webpack 中配置 preload
preload-webpack-plugin 是 html-webpack-plugin 插件的一个扩展,所以需要搭配使用。
例如配置 preload :
plugins: [
new HtmlWebpackPlugin(),
new PreloadWebpackPlugin({
rel: 'preload',
as(entry) {
if (/\.css$/.test(entry)) return 'style';
if (/\.woff$/.test(entry)) return 'font';
if (/\.png$/.test(entry)) return 'image';
return 'script';
},
include: ['app']
})
]
复制代码
最终在html注入为:
<link rel="preload" as="script" href="app.31132ae6680e598f8879.js"> 复制代码
在 Webpack 中配置 prefetch
prefetch 配合 Vue 中的路由懒加载代码分割更好用
因为本项目可视化 工具 中没有使用路由,没有配置 prefetch 。
优化package
目前项目中比较常用的工具类库有 lodash、moment、element-ui,对于这些经常使用的类库可以通过Dllplugin 分离依赖成一个静态资源库。一般不会去改动这个依赖包版本。
不过像lodash、moment是有其他方法来减少打包体积的。
-
按需加载
element-ui,见官方文档 -
按需加载
lodash
一般我们使用 lodash 时,不会用到其中所有的函数。有可能用到了几个,这时候可以选择按需引入 lodash,不要引入全量。下面通过安装两个插件:
npm i babel-plugin-lodash lodash-webpack-plugin -D 复制代码
配置 .babelrc 文件
"plugins": [ "lodash" ] 复制代码
- 使用
dayjs代替moment,API基本一样,使用后会发现大部分场景都能使用,而且打包只有 7KB 。
升级 HTTP2
可视化工具中组件变得越来越丰富,随之带来的页面请求数据接口也逐渐变多,开销在逐渐增大。单个页面数据接口请求几十上百不等。
如果继续使用HTTP1.x,大家都懂的,HTTP1.x协议的局限性,大多数现代浏览器都支持同时一个主机最大请求数量为6个,也就是说,如果这6个接口请求没有返回结果处于 pending 状态的话,页面就一直刷不出数据,这样给用户的体验是很差的。HTTP2的多路复用解决了这个问题,我们通过将服务器升级为 HTTP2 增大了浏览器请求连接吞吐量,大大提升了应用的性能。
HTTP2 简介
HTTP2.0 可以让我们的应用更快、更简单、更健壮 --- 《Web性能权威指南》
HTTP 2.0 的目的就是通过支持请求与响应的多路复用来减少延迟,通过压缩 HTTP 首部字段将协议开销降至最低,同时增加对请求优先级和服务器端推送的支持。
HTTP 2.0 性能增强的核心,全在于新增的 二进制分帧层 ,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
HTTP 2.0 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应地,很多流可以并行地在同一个 TCP 连接上交换消息。
HTTP 2.0 的 二进制分帧 机制解决了 HTTP 1.x 中存在的队首阻塞问题, 也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,就是应用速度更快、开发更简单、部署成本更低。
HTTP2 优化
域名分区
使用 HTTP 缓存
缓存应用资源,避免每次请求都发送相同的内容。浏览器在下载静态资源后,使用缓存将下载过的资源维护好,这样下次加载网页时直接使用本地的副本。减少了资源请求以及等待时间。
Cache-Control
通用的HTTP请求头首部字段,只需指定一个明确的缓存时间即可。可以配置在 nginx 配置文件里。
location ~ .*\.(js|css|ttf|svg|ico){
add_header Cache-Control max-age=86400;
}
复制代码
页面第一次加载
再次加载
缓存验证
可以看到加入缓存后, Status Code 为 200 OK (from memory cache),缓存时间为: max-age=86400
Vue 批量渲染组件
业务场景中,随着应用变得越来越复杂,加载一个页面可能需要渲染过多的组件,渲染多个组件有两种策略:
- 遍历所有组件,每一个接口请求返回数据时去渲染组件
- 请求所有接口,所有数据返回时批量渲染组件
通过实践发现,后者渲染更快,后者消除了每次请求接口之后渲染组件的时间,因为多次渲染组件会带来额外的 Scripting 开销,比如Vue中的 computed 或 watch ;同时结合 HTTP2 的多路复用,请求多个接口也会很快的响应。
示例代码:
// 批量更新组件方法
batchUpdateComponent({ dispatch }, promises) {
// 请求所有接口
return Promise.all(promises.map(p => p.catch(() => undefined)))
.catch(err => {
console.log(err)
})
.then(res => {
// 一次性渲染组件
res && dispatch('updateComponent', res)
})
}
复制代码
:bulb: 如果 Promise 的 catch 回调返回了 undefined,那么 Promise 的失败就会被当做成功来处理。 使用 ES2018 的提案 Promise.finally
Vue 异步组件
项目中应用业务代码量在不断攀升,写了很多业务组件,其实在一定场景下,并非所有组件都需要渲染,比如,可视化工具有编辑模式和预览模式。编辑模式需要使用 Code Mirror 用来编写一些 SQL 语句,预览模式时候就不需要使用。
组件正常引入:
import CustomSql from '@/components/CustomSql'
export default {
components: {
CustomSql
}
}
复制代码
组件异步引入:
// ES6 结合 Webpack
export default {
components: {
CustomSql: () => import('./CustomSql')
}
}
复制代码
Vue中路由懒加载就是使用 异步组件 和 Webpack 的 代码分割功能 实现的。
SVG优化
随着项目中组件的增多,组件的icon随之也变的多了。大部分icon是svg格式,我们可以使用 SVG Sprite 技术管理SVG图标。
SVG Sprite 技术
所谓 SVG Sprite 类似于CSS中的 Sprite 技术。将图标整合在一起,实际呈现的时候准确显示特定图标。
SVG Sprite 技术最佳实践是:
symbol use
使用例子:
<svg> <!-- symbol definition NEVER draw --> <symbol id="sym01" viewBox="0 0 150 110"> <circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red"/> <circle cx="90" cy="60" r="40" stroke-width="8" stroke="green" fill="white"/> </symbol> <!-- actual drawing by "use" element --> <use xlink:href="#sym01" x="0" y="0" width="100" height="50"/> <use xlink:href="#sym01" x="0" y="50" width="75" height="38"/> <use xlink:href="#sym01" x="0" y="100" width="50" height="25"/> </svg> 复制代码
组件化 SvgIcon
基于 Vue 封装的 SVG ICON 组件
// @/components/SvgIcon.vue
<template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
return 'svg-icon ' + this.className
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
复制代码
自动化引入 SVG
将 src/assets/icons 下所有icon动态引入
// @/plugins/svgicon.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'
Vue.component('svg-icon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const svgIcons = require.context('./components', false, /\.svg$/)
requireAll(svgIcons)
复制代码
打包 SVG Sprite
我们可以用 svg-sprite-loader 这个插件来生成 SVG Sprite ,通过组件的方式引入 svg icon。
基于 Webpack 3.x 的配置方法如下:
// 通过 exclude/include 来区分哪些属于svg icon,哪些属于image
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
exclude: [resolve('src/assets/icons')],
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/assets/icons')],
options: {
symbolId: 'icon-[name]'
}
}
复制代码
总结
本次性能优化关键点:
Webpack方面:
- 开启Gzip,直接收益比较大
- 使用preload插件,预先声明要使用到的资源
- 尽可能优化package,做到按需加载,减少打包体积
网络方面:
- 升级服务器为HTTP2,结合HTTPS是最佳实践
- 使用 HTTP 缓存策略,最好的性能是 不用请求
Vue实践方面:
- 渲染组件时机,建议在全部接口请求返回后去批量渲染
- 将不常用的特定场景下使用的组件写成异步组件
资源方面:
- 项目中使用较多SVG时,可以选择使用“SVG Sprite”技术管理
最后
项目初始,由于工期紧张,我们急着迭代功能,目标是交付功能完备的应用,用户量增长的时候就该停下来好好考虑考虑如何提升应用的性能了。纵使应用的功能再完备,如果用户体验非常差,那是不是值得反思,性能优化是一件需要持续做的事情。
我想借用一下《Web性能权威指南》里, Ilya Grigorik 提到的:“:bulb:我们关心的不止是交付能用的应用,我们目标是交付最佳性能!” 来总结性能优化的实践,同时提醒自己,在做项目的时候尽可能的提前想到性能优化的点。
以上所述就是小编给大家介绍的《???? 记一次前端性能优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript Web应用开发
[阿根廷] Nicolas Bevacqua / 安道 / 人民邮电出版社 / 2015-9 / 59.00元
本书是面向一线开发人员的一本实用教程,对最新的Web开发技术与程序进行了全面的梳理和总结,为JavaScript开发人员提供了改进Web开发质量和开发流程的最新技术。本书主要分两大块,首先是以构建为目标实现JavaScript驱动开发,其次介绍如何管理应用设计过程中的复杂度,包括模块化、MVC、异步代码流、测试以及API设计原则。一起来看看 《JavaScript Web应用开发》 这本书的介绍吧!
SHA 加密
SHA 加密工具
XML、JSON 在线转换
在线XML、JSON转换工具