学习
实践
活动
专区
工具
TVP
写文章
专栏首页思衍 Jax 专栏企鹅辅导课程详情页毫秒开的秘密 - PWA 直出
原创

企鹅辅导课程详情页毫秒开的秘密 - PWA 直出

天下武功,唯 (wei) 快(fu) 不(bu) 破(po)。

随着近几年的前端技术的高速发展,越来越多的团队使用 React、Vue 等 SPA 框架作为其主要的技术栈。以 React 应用为例,从性能角度,其最重要的指标可能就是首屏渲染所花费的时间了。那么今天,我们要给大家分享的一个把优化做到极致的故事。

我们的目标是让 H5 的页面也能够拥有 Native 般的体验,如果你还在寻求什么技术能够让老板虎躯一震(拯救你的KPI),那么这篇文章或许能够帮助到你。

企鹅辅导课程详情页是什么

企鹅辅导详情页

课程详情页是腾讯旗下 企鹅辅导 APP 中最重要页面之一,也是流量最大的页面之一,所以它的打开速度也是至关重要的。

这是一个使用 React 编写的 H5 页面,运行于多端,包括: 企鹅辅导APP手机 QQ手机浏览器

架构演变

纯异步渲染

我们知道当前主流的 SPA 的应用的默认渲染方式都是这样的:

在这种情况下,从加载页面到用户看到页面(首屏渲染所花费的时间)就是上图中灰色边框区域所包括的时间。

这是最慢的一种方式,就算 CGI 够快,最少要花费 1S2S 左右的时间了。

接着我们简单优化一下:

  • 把静态资源缓存起来,这样下次用户打开的时候就不用从网络请求了。
  • 步拉取 CGI 这个动作是否可以提前呢?我们可以在请求 HTML 之后,先通过一段 JS 脚本去请求 CGI 数据,后面第 步的时候,就可以直接拿到数据了,这就是 CGI 预加载

怎么做到呢?我们的方案是统一封装 Request 请求工具,在用 Webpack 打包的时候,会往页面顶部注入一段 预加载 CGI 的 JS 代码,维护一个CGI 与 DATA 对应 MAP,后面发请求的时候,先去 MAP 里取值,如果有值的话直接拿出来,没有的话则发起HTTP 请求。(具体请查阅我们团队开源的 Preload 工具)

这种模式还有一些其他的优化的方法:

  • 在 HTML 内实现 Loading 态或者骨架屏;
  • 去掉外联 css;
  • 使用动态 polyfill;
  • 使用 SplitChunksPlugin 拆分公共代码;
  • 正确地使用 Webpack 4.0 的 Tree Shaking;
  • 使用动态 import,切分页面代码,减小首屏 JS 体积;
  • 编译到 ES2015+,提高代码运行效率,减小体积;
  • 使用 lazyload 和 placeholder 提升加载体验。

这种模式的优化不是我们这次讲述的重点,想了解的童鞋可以查看这篇文章

效果如下图所示:

异步渲染
直出同构

在异步的模式下,除了上述优化,我们还在端内(企鹅辅导 APP、手机 QQ)内做了离线包缓存(腾讯手Q方面独立研发出来的针对手机端优化的方案,简而言之就是将静态资源缓存在手机 APP 内),经过我们的数据测试,首屏渲染大概能够达到秒开(1s左右) 的效果。

-w300

但对有着性能极致追求的我们来说,肯定是不会满意的。

继续优化,最容易、最大众的套路肯定就是直出(服务端渲染)了。

现在直出的方案已经有很多很多种,这里也不多做介绍了,如果您想了解更多关于服务端渲染的方案,请参考这篇文章。

直出针对首屏时间的优化效果是非常明显的,经过我们的测试,数据大概能够提升25%左右。

直出之后的效果如下图:

直出同构

可以看到对于首屏来说,没有了【加载中...】的等待时间,视觉体验提升了不少。

PWA 直出

PWA

针对上述、常见的直出应用来说,我们能够优化的点在哪里呢?让我们来详细分析一波,这也是今天我们要给大家分享的重点。

首先看看直出应用各个环节的耗时表 (本地环境 2018款 iMac):

过程名称

过程花费

Node 内 CGI 拉取

300 ms

RenderToString

20 ms

网络耗时

10 ms

前端HTML渲染

30 ms

从上面的表中我们看出,直出渲染的耗时的大头还是在 CGI 接口的拉取上。

我们现在提出两个问题

  • CGI 接口的数据是否可以缓存 ?
  • HTML 又是否可以缓存 ?
一、接口的动静分离

动态信息

这个页面的接口数据中,有一些数据,是实时变动的, 比如:当前还剩多少个名额、此时此刻课程的价格、用户是否购买过这个课程等。

这些数据的特性决定了这个数据接口不能够被缓存。(假设将其缓存,那么就会存在可能用户进来看到当前还剩下10个名额,其实课程已经卖光了的情况)

为了这个时间耗时的大头,我们做了CGI接口的动静分离

将与用户态、当前时间没有关联的数据(比如课程标题课程上课的时间试听模块的地址等)放在一个接口(静态接口),其他变化的数据放在另一个接口(动态接口)。

那么可以使用静态的接口来做服务端渲染,好处是第一比较快(少了动态的信息,而且后台也可以做缓存),第二 Node 直出可以做缓存了。

二、直出 Redis 缓存

这样我们就可以将那部分静态的、不会经常变动的数据用来直出 HTML,然后将这个 HTML 文件缓存到 Redis 中

客户端请求此网页,Node 端接受到请求之后,先去 Redis 里拿缓存的 HTML,如果 Redis 缓存没有命中,则拉取静态的 CGI 接口渲染出 HTML存入 Redis。

客户端拿到 HTML 之后,会立刻渲染,然后再用 JS 去请求动态的数据,渲染到相应的地方。

做完之后我们可以看到优化效果的提升是非常非常明显的:

直接从 262ms 提升到了 16ms !(本地环境),简直飞一般的感觉,妈妈再也不用担心领导看耗时了。

三、PWA 直出缓存

关于什么是 PWA ,以及如何使用,请移步这篇文章。

做了 Node 端直出的 HTML 缓存之后,我们接着优化,接着思考,是否可以在客户端也缓存 HTML,这样连网络延时这部分消耗也省掉呢。

答案就是使用 PWA 在客户端做离线缓存,将我们直出的 HTML 缓存在客户端,每次用户请求的时候,直接从 PWA 离线缓存里取出对应的直出页面(HTML)响应给用户,响应之后紧接着请求 Node 服务更新本地的 PWA 缓存。(如下图所示)

核心代码:

self.addEventListener("fetch", event => {  
 // TODO other logic (maybe fetch filter)

  // core logic
  event.respondWith(
    caches.open(cacheName).then(function(cache) {
      return cache.match(cacheCourseUrl).then(function(response) {
        var fetchPromise = fetch(cacheCourseUrl).then(function(
          networkResponse
        ) {
          if (networkResponse.status === 200) {
            cache.put(cacheCourseUrl, networkResponse.clone());
          }
          return networkResponse;
        });
        return response || fetchPromise;
      });
    })
  );
});

废话不多说,先看效果对比 (左 PWA 直出;右 离线包):

duibi

从上图可以看出,使用了 PWA 直出缓存之后,首屏渲染基本是毫秒开,可以说与 Native 并肩了。

经过我们的数据测试,使用 PWA 直出缓存,首屏渲染的时间最好可以到400ms左右级别:

PWA 直出细节优化

一、防页面跳动

因为对接口进行了动静分离,使用静态接口直出页面,然后在客户端拉取动态数据渲染完。这就可能会导致页面的抖动(比如详情页中的试听模块,是在客户端渲染的)。

因为高度改变了,视觉上就会出现抖动(具体可以参考上面章节直出时候的 GIF 截图)。

要去掉页面抖动的情况,就必须保证容器的高度在直出时候已经存在了

比如这个试听模块,其实这个封面图和试听按钮是可以在服务端渲染出来的,而后面的 Video 模块则必须要在客户度渲染(腾讯云 Tcplayer)。

所以这里可以拆分成:(试听封面 + 按钮 + 时间)服务端渲染 + 底层 Video(客户端渲染)。

有些需要在客户端计算高度的容器(表现为常放在 ComponentDidMount 里计算),如果它们依赖客户端环境(比如依赖当前系统是安卓还是 IOS),就导致他们肯定不能放在服务端直接渲染出来,这又怎么办呢?

这里我们的做法,是将这些计算放在 HTML body 之前,通过内联的脚本嵌入,计算出当前环境,给 body 加上一个特定的类(class),然后在这个特定的类下面的元素,就可以通过 css 给予特定的样式。比如下面代码:

/*
 * 因为在不同的手机 APP 环境内,页面的 padding 是不一样的。
 * 我们要在页面渲染完之前加上相应的 padding 
 */
var REGEXP_FUDAO_APP = /EducationApp/;
if (
  typeof navigator !== "undefined" &&
  REGEXP_FUDAO_APP.test(navigator.userAgent)
) {
  if (/Android/i.test(navigator.userAgent)) {
    document.body.classList.add("androidFudaoApp");
  } else if (/iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) {
    if (window.screen.width === 375 && window.screen.height === 812) {
      document.body.classList.add("iphoneXFudaoApp");
    } else {
      document.body.classList.add("iosFudaoApp");
    }
  }
}
.androidFudaoApp .tt {
  padding-top: 48px;
  background-position-y: 84px;
}

.iphoneXFudaoApp .tt {
  padding-top: 88px;
  background-position-y: 124px;
}

.iosFudaoApp .tt {
  padding-top: 64px;
  background-position-y: 100px;
}

然后把这段代码通过构建插入到页面 body 之前。

-w500

防抖动优化效果如下 (左优化完,右未优化):

duibi_doudong
二、冷启动预加载

虽然我们做了 PWA 离线缓存,但是对于冷启动来说,客户端里面的 PWA 缓存还是没有的,这样就会导致初次点击页面,渲染速度相对慢一点。

这里我们可以在 APP 启动的时候,用一个预加载的脚本最大限度的拉取用户可能访问的页面。

核心代码如下:

// 预加载页面时, PWA 预缓存课程详情页面的直出
function prefetchCache(fetchUrl) {
    fetch("https://you preFetch Cgi")
      .then(data => {
        return data.json();
      })
      .then(res => {
        const { courseInfo = [] } = res.result || {};
        courseInfo.forEach(item => {
          if (item.cid) {
            caches.open(cacheName).then(function(cache) {
              fetch(`${courseURL}?course_id=${item.cid}`).then(function(
                networkResponse
              ) {
                if (networkResponse.status === 200) {
                  cache.put(
                    `${courseURL}?course_id=${item.cid}`,
                    networkResponse.clone()
                  );
                }
                // return networkResponse;
              });
            });
          }
        });
      })
      .catch(err => {
        // To monitor err
      });
}

PWA 直出遗留问题

一、兼容性问题

随着 PWA 技术的发展,现今大部分手机以及 PC 环境已经支持对 PWA 进行了支持。经过我们的测试发现:安卓基本上都是支持的,IOS 需要11.3以上才支持。

Service Workers 兼容性图

具体的兼容性支持点我查看

二、IOS 渲染问题

很多的经验告诉我们,外联的 script 标签要放在 body 的后面,因为它会阻塞页面的 DOM 渲染。

经过测试发现,IOS 的 WebView (UIWebView)渲染机制并不会上述一样,而是要等到后面的 JS 执行完之后才渲染页面,如果是这样,我们的直出渲染优化就没有效果了(因为 HTML 并不在最开始渲染),这里可以使用 script 标签的 asyncdefer 属性来达到异步渲染的作用。

升级 WkWebView 之后,情况得到改善,渲染正常。

附录

参考资料

更多基于 PWA 的性能优化实践,请查看 IMWeb 团队刘华的分享

原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

登录 后参与评论
0 条评论

相关文章

  • 企鹅辅导课程详情页毫秒开的秘密 - PWA 直出

    本文由 IMWeb 团队成员 Jax 首发于腾讯内部KM论坛。点击阅读原文查看 IMWeb 社区更多精彩文章。 天下武功,唯 (wei) 快(fu) 不(bu...

    用户1097444
  • 带你走进PWA在业务中的实践方案

    本文由 IMWeb 团队成员 lq 首发。点击阅读原文查看 IMWeb 社区更多精彩文章。 注:本文需要有一定的 PWA 基础 1. 什么是 PWA? 要知道一...

    用户1097444
  • 该用什么姿势来使用 PWA

    回答 what 这种问题,重点在于名词,因此 PWA 是一个 APP,一个独立的、增强的、Web 实现的 APP

    IMWeb前端团队
  • Serverless 中文社区采访

    Serverless 中文社区采访 1、请您向读者做一下自我介绍。 蒋林源,就职于腾讯在线教育,腾讯高级前端工程师,IMWeb 团队成员,腾讯企鹅辅导 SSR ...

    用户1097444
  • 腾讯企鹅辅导 H5 性能极致优化

    H5 项目是企鹅辅导的核心项目,已迭代四年多,包括了课程详情页/老师详情页/报名页/支付页面等页面,构建产物用于企鹅辅导 APP/H5(微信/QQ/浏览器),迭...

    winty
  • 腾讯企鹅辅导 H5 性能极致优化

    企鹅辅导 H5 页面在长期迭代过程中,逐渐累积了一些性能问题,导致页面加载、渲染速度变慢。为了提升用户体验,近期针对页面加载速度,渲染速度做了专项优化,本文是对...

    用户1097444
  • Serverless SSR 技术在「腾讯在线教育」中的实践

    腾讯在线教育团队在传统的 Web 应用方向其实有很多技术方面的尝试,包括传统离线包、PWA 离线应用等,但是每个技术栈都有其优点与缺点,目前团队的技术方案对比如...

    腾讯云serverless团队
  • TWeb Conf 2019 精彩回顾

    大会概述 2019年11月16日,首届TWeb Conf前端大会在深圳科兴国际会议中心成功举办。大会现场参会人员近500人,线上直播课程报名人数近200人。通过...

    用户1097444
  • 腾讯推出AI作业辅导神器“鹅童”,这届老母亲有救了!

    给娃辅导作业是一种什么体验? 请看家长们现身说法: 33岁妈妈因给孩子辅导作业 急性脑梗住院 80后家长朋友圈求嫁幼女 只为不用给娃辅导作业 陪娃做作业捶桌...

    鹅老师
  • lottie系列文章(一):lottie介绍

    企鹅辅导是一款处于快速上升期的产品,目前在快速迭代中。作为一款K12青少年教育产品软件,动画对于吸引其用户注意力和提高用户体验有着重要的作用。特别是在目前开放了...

    IMWeb前端团队
  • 山东青州“抗疫”实录:7天完成智慧校园升级

    新冠病毒疫情的突袭,给教育行业带来了巨大的挑战:全国大中小学集体从线下“搬迁”到线上,且时间紧、任务重。然而,在如此重压之下,山东省青州市还主动给自己增加了难度...

    鹅老师
  • 小程序自动化测试总结

    本文由 IMWeb 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。 一、缘起-为什么要进行小程序自动化测试 微...

    用户1097444
  • 腾讯教育的战场、诗歌和远方

    文章转自公众号浅黑科技 原标题《腾讯进军教育:一半战火,一半田园诗》 我在浅黑科技的知识星球里曾经介绍过科普达人刘大可的一篇神文《如果人类的物质文明瞬间消失。...

    鹅老师
  • IMWebConf 2016总结

    本文作者:IMWeb link 原文出处:IMWeb社区 未经同意,禁止转载 ? 9月10号,IMWeb团队在腾讯大厦成功举办了IMWebConf 2...

    IMWeb前端团队
  • IMWeb Conf 2016(腾讯IMWeb前端技术大会)精彩回顾

    9月10号,IMWeb团队在腾讯大厦成功举办了IMWebConf 2016!进行了一次十分精彩的分享沙龙!(大会沉淀,PPT、视频等干货请猛戳阅读原文!) 一、...

    用户1097444
  • 腾讯课堂点播上云客户端实践总结

    点播业务目前用的是 HLS 协议。HLS协议全称是 HTTP Live Streaming,它是一个由苹果提出的基于HTTP的流媒体网络传输协议。HLS协议规定...

    腾讯云开发者
  • 在线教育大前端架构演进之路

    前段时间,本人有幸于在深圳GMTC大前端架构演进专场进行分享。其后应叶冉编辑邀请,总结了此次分享的演讲稿《腾讯在线教育大前端架构演进之路》。首先做一下自我介绍。...

    用户1097444
  • 硬核实践经验 - 企鹅辅导 RN 迁移及优化总结

    本文由 IMWeb 团队成员 JaxJiang 首发于腾讯内部 KM 论坛。点击阅读原文查看 IMWeb 社区更多精彩文章。 导语 本文阅读时间大约需要 8 分...

    用户1097444
  • 五分钟了解互联网Web技术发展史

    作者:charryhuang,腾讯 CSIG 前端开发工程师 1991年8月,第一个静态页面诞生了,这是由Tim Berners-Lee发布的,想要告诉人们什...

    腾讯技术工程官方号

扫码关注腾讯云开发者

领取腾讯云代金券