???? 记一次前端性能优化

栏目: 后端 · 前端 · 发布时间: 4年前

内容简介:工作中一直在做一款公司内部的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 toolsNetwork 查看网络链接 Request Headers 中出现 Accept-Encoding: gzip 即生效。

???? 记一次前端性能优化

使用 Preload 插件

???? 记一次前端性能优化

:bulb: 使用 Resource Hints 中的preload 与prefetch 来提升应用的性能。

关于 preloadprefetch

<link rel="preload"> 是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。

<link rel="prefetch"> 是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

在 Webpack 中配置 preload

preload-webpack-pluginhtml-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中的 computedwatch ;同时结合 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:我们关心的不止是交付能用的应用,我们目标是交付最佳性能!” 来总结性能优化的实践,同时提醒自己,在做项目的时候尽可能的提前想到性能优化的点。


以上所述就是小编给大家介绍的《???? 记一次前端性能优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员面试宝典

程序员面试宝典

欧立奇、刘洋、段韬 / 电子工业出版社 / 2006-7 / 39.00元

本书取材于各大IT公司历年面试真题(包括笔试题、口试题、电话面试、英语面试,以及逻辑测试和智商测试)。通过精确详细的分类,把在应聘程序员(含网络、测试等)过程中所遇见的常见考点分为21章。不仅对传统的C系语言考点做了详尽的解说,包括面向对象问题、sizeof问题、const问题、数据结构问题等。还根据外企出题最新特点,针对设计模式问题、C#问题、网络问题、数据库问题、NET问题等,做了深入的说明。......一起来看看 《程序员面试宝典》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试