blog icon indicating copy to clipboard operation
blog copied to clipboard

前端优化 - 记一次前端优化项的落地

Open HXWfromDJTU opened this issue 4 years ago • 0 comments

前言

过去两年的时间有幸可以去主导多个 toC 的 web 项目,随着用户量的不断增大,也就让“朗读并背诵全文”的全前端优化手段们得到了实践的机会。

项目的基本信息:

  • 项目基础框架使用的是 nuxt.js
  • 数据上报使用的是 Google Analytics
  • 错误监控使用的是开源的 Sentry
  • 前端项目和后端api都是部署在阿里云上

接下来说说自己在过去一年多里,优化项目是如何在项目中落地。

优化起步

随着项目功能模块地增多、用户量也在不断地积累,运营同学那边收到用户的反(to)馈(cao)越来越多...

  • *xxx is great, but the xxx on web sometime drive me crazy... 😡

  • 一个帮我们推广应用的大V 在推广视频中,面对首页的 8s 加载时间,竟然习以为常地吹起了口哨,平静地向粉丝说 “Dont worry, It just usually take a while.”

天天忙着改 bug 的我们组还没来得及看运营小姐姐的的消息,就被老板在群里 艾特了。


@前端组-ALL 这样的体验,凭我们凭什么留住用户???

两张图重重地砸在了我的脸上,将近 10s 的平均加载时间,和主要用户来源马来西亚的加载时间甚至达到了 11.41s

随手打开线上的项目主页,就碰到了和 大V 哥一样的尴尬.....,nuxt 的loading 足足占据了屏幕 15s 才缓缓出现内容。

要我自己是用户早就关掉了......

定位问题

“平均网页加载时间”

冤有头债有主,解铃也还需寄铃人嘛。既然是 “平均网页加载时间” 惹了众怒,那就查查 Google Analysis 对这个词儿是如何定义的吧。 传送门👉

Google Analytics 的描述和我们web开发中的 load 事件基本一致。

当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件。 --- MDN

问题根源

通过具体分析得知,有以下几个点影响了我们的 load 时间...

  1. 未登录状态下,加载了一个第三方的脚本,用于加载一个授权登录的iframe页面,但由于各个国家墙的原因,这个脚本静态会加载失败
  2. 已登录状态下,用户的资产页面会有大量的代币资产logo图片需要加载
  3. webpack打包后,页面的主要 Javascript 文件大小太大,网络加载速度也很慢,直接导致了用户看到大大的loading图标过久。

优化办法

图片延迟加载

你一定也遇到过首屏是列表页,每个列表项上都有一个或者多个图片需要加载,大量的图片同时加载时间十分不确定,同时渲染到 DOM 上也会造成页面的卡顿。

老问题了,方法也多:

  • 改接口,分页渲染数据项
  • 一次性获取数据,但是前段使用 “卷帘” 等手段进行资源懒加载

结果未知的外链脚本

多数情况下,这部分脚本的加载,不会影响到首屏的主观体验。但当首屏加载完成,用户主动去触发该功能时,需要做好所对应功能 “加载未完成” 等提示。

const targetScript = document.getElementById('targetScript')   

targetScript && targetScript.onload = () => {
   const authIframe = document.getElementById('auth-iframe')

   authIframe && authIframe.onload = () => {
       loaded = true
   }
}

在自己合适的地方自定义 page load 事件,这样使得 Google Analytics 上的数据更直观。

ga('send', 'timing', 'JS Dependencies', 'load', timeSincePageLoad);

因为实际上我们需要的是 TTI (可交互时间)。这部分内容可以参考这篇笔记 👉

关键资源加载速度慢

资源加载速度慢有两个方面,一个是资源本身体积过大,二是网络传输速度太慢。

资源压缩

webpack打包优化网上的资料很多,这里就不拓展开讲了。这次只是习惯性地运行一次 analyser,便可以发现那个眨眼的 lodash.js

// 全部引入
import { toNumber, sortby } from 'lodash-es'

// 改为单独引入
import toNumber from 'lodash.toNumber'
import sortby from 'lodash.sortby'
CDN加速

上面的一顿操作后,JavaScript 文件的大小的到了控制,但海外用户反映还是慢慢慢慢慢慢慢慢慢慢慢慢.....

这时候针对性地上了一个部分地区的 CDN 服务。域名配置的是 static-xxx.io

根据 Nuxt.js 的文档(传送门👉)配置一下,发布前尝试一下连通性...就可以发到线上试试啦。

export default {
  build: {
    publicPath: 'https://static-xxx.io/_nuxt/'
  }
}

实际效果也比较明显

缓存的使用

随着 Web 应用的不断复杂化,早就已经不是填写一个表单就离开的那个时代了。特别是在移动端使用你的 web 服务时,总是想要体会到和原生 App 一样的感受。此时,本地缓存就变得越来越重要了。

干掉loading

无论是作为用户还是开发者,你是不是也受够这无穷无尽 loading。滥用 loading 作为你页面的遮羞布,长此以往只会让你的页面越来越不可用。

优化手段

  1. 页面加载时首先使用本地缓存展示页面
  2. 使用脚本 ajax 或者 websocket 进行静默地数据更新
  3. 若本地没有缓存,才使用 loading 进行数据更新,更新后也缓存到本地

对于 loading 提醒的其他建议,可以看看这篇笔记

减少网络延迟

减少请求握手

针对于数据实时性要求强的业务模块,请求的信道的搭建时间已经远远地超过了数据的实时性本身。这次使用的是 websocket 的解决方案。

在减少了请求我收的同时,也带来了一些问题:

  1. 处理流程与现有的 http 请求不兼容
  2. 鉴权机制也需要特殊调整

具体操作请参考这篇笔记👉

减少跨域预请求

数据请求一般要求下都是发送的非简单请求,这其中就会包括预请求的时间。使用 Access-Control-Max-Age: xxx 减少预请求的次数。

详细的原理请参看这篇笔记👉

减少 DNS 时延

prefetch 是浏览器的一种机制,可以利用空闲时间提前先帮你下载好 未来 可能需要的资源。

  1. dns-prefetch 可以帮助我们在用户仅仅访问主页的情况先,优先帮助用户完成 DNS 查询,并缓存结果再浏览器中。

    <!-- 十分可能访问页面 -->
    <link rel="dns-prefetch" href="https://doc.xxx.io/" >
    <link rel="dns-prefetch" href="https://home.xxx.io/" >
    <!-- 肯定会用到的接口URL -->
    <link rel="dns-prefetch" href="https://api.xxx.io/" >
    <link rel="dns-prefetch" href="https://login.xxx.io/" >
    
  2. 类似于 http 2.0的 server push的意思,前端也可以要求浏览器提前把需要的 cssfont 等资源提前下载到缓存中。并结合资源的 max-ageexpire 缓存策略达到跨页面使用。

    <link rel="prefetch" href="telegram.auth.com/123ahisdhu2.js" />
    

MDN 连接: [dns-prefetch] [prefetch]

未完待续....

参考资料

  1. Caching Components - Nuxt.js
  2. Nuxt中如何做页面html缓存 - juejin.im
  3. Prefetch, Preload - segmentfault
  4. 使用 Preload/Prefetch 优化你的应用 - 知乎

HXWfromDJTU avatar Nov 10 '20 08:11 HXWfromDJTU