内容简介:工作中一直在做一款公司内部的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:我们关心的不止是交付能用的应用,我们目标是交付最佳性能!” 来总结性能优化的实践,同时提醒自己,在做项目的时候尽可能的提前想到性能优化的点。
参考
《Web性能权威指南》
以上所述就是小编给大家介绍的《???? 记一次前端性能优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript凌厉开发
张鑫 黄灯桥、杨彦强 / 清华大学出版社 / 2010 年4月 / 49.00元
本书详细介绍Ext JS框架体系结构,以及利用HTML/CSS/JavaScript进行前端设计的方法和技巧。作者为Ext中文站站长领衔的三个国内Ext JS先锋,在开发思维和开发经验上有着无可争议的功力。 本书包含的内容有Ext.Element.*、事件Observable、Ext组件+MVC原理、Grid/Form/Tree/ComboBox、Ajax缓存Store等,并照顾JavaSc......一起来看看 《JavaScript凌厉开发》 这本书的介绍吧!