Giter Club home page Giter Club logo

article's People

Stargazers

 avatar

Watchers

 avatar  avatar

article's Issues

不到 0.3s 完成渲染!360 信息流正文“闪开”优化实践

不到 0.3s 完成渲染!360 信息流正文“闪开”优化实践

不到 0.3s 完成渲染!360 信息流正文“闪开”优化实践

开篇之前先介绍一下场景。信息流是一个基于用户兴趣使用算法将用户感兴趣的新闻内容推荐给用户的一种业务。这种业务带有非常特色的场景就是用户有一个“永远”都刷不完的推荐流列表,点击列表中的新闻之后可以跳转到其详情页中查看新闻的正文内容。列表一般都是由客户端原生去实现的,而详情页这块由于新闻内容结构的复杂性,一般还是会使用 h5 来实现。这样就对我们 h5 的性能提出了要求,我们必须在用户切换的时候将切换的白屏时间尽量减少,这样才能提高用户的阅读体验。

本文就将为大家讲述一下我们是如何实现性能优化达到“闪开”的效果的。我们可以先看看效果,下图左边是正常版本,而右边的是优化后的版本。对比之下可以发现即使我已经悄咪咪的先点击左边的手机,同一篇新闻右边的打开速度明显比左边的要快很多。接下来就让我们看看这个是如何做到的吧!

目前现状

众所周知,网页中内容渲染往往根据渲染方式可以分为后渲染和前端渲染两种方式,最近几年由前端渲染又演化出了同构渲染,也就是大家经常说的 SSR。这几种渲染方式的主要优缺点大概整理了主要有如下几个方面。

  1. 后端渲染:

    • 优势:服务端直出首屏性能好,SEO好
    • 劣势:交互逻辑复杂需要两端维护结构
  2. 前端渲染:

    • 优势:前端交互易维护,数据渲染分离
    • 劣势:首屏性能问题以及 SEO 问题
  3. 同构渲染:

    • 优势:首屏性能好,SEO 好,一份代码多端运行
    • 劣势:代码维护成本,服务器性能和维护成本增加

当然本篇文章不是来讲各种渲染方式的优缺点的,主要是说因为种种原因我们的项目最后使用了前端 JS 渲染的方式。而 JS 渲染带来的性能问题主要是由于数据接口请求返回以及前端 JS 资源获取所带来的网络问题。为了解决这两个问题,一方面我们采用了服务端将数据注入到页面全局变量中的方式避免了数据请求,另一方面我们使用了 localStorage 缓存的方式将前端资源做了 LS 缓存避免了二次打开之后的前端资源请求,从而提高了前端渲染的首屏性能。

思考优化方案

虽然我们避免了前端渲染的一些问题对首屏的性能做了优化,但还远远不够。那目前还有哪些点可以进行优化呢?简单的整理了下可以有如下两个方面:

  • 首次进入以及线上代码有更新之后还是需要下载前端资源
  • 服务端页面的 ttfb 相应还有优化的空间
  • 客户端 WebView 打开的速度和性能还有优化的空间

从上面两个优化点我们可以看到所有的优化还是网络的优化,主要还是在移动端网络对性能的影响是远远大于其他方面的。那么是否有什么方案能够让我们免去这些网络请求呢,最终我们给的答案就是详情页本地化。通过本地化方案,我们将平均 820ms 的首屏渲染时间优化到了 260ms,整整提高了三倍多

详情页本地化就是客户端不走网络请求打开新闻的方案,解决上文中列举的所有网络请求相关的优化点。它除了能为我们带来首屏性能的进一步提升之外,由于它不走网络请求的特性,也为我们解决了复杂网络环境下页面劫持导致的详情页白页打不开的问题。同时还为我们带来了无网络环境下的离线阅读新闻的能力。

本地化实现

由于我们的这面是纯 JS 渲染的,所以我们一个最终的详情页主要是由新闻数据静态页面两者构成的。 鉴于对服务端的依赖非常的少,和大部分的 SPA 页面一样,本质上只要在客户端将我们的前端页面提前下载下来就能正常打开了。

详情页 = 静态页面 + 新闻数据
复制代码

数据预下发

而如何在用户还没有打开新闻之前客户端就能把我们的页面资源下载下来呢?这里就不得不提一下我们的场景,因为在我们的信息流场景中,用户永远是通过流点击进入到详情页中。而在客户端的流中是需要加载服务端数据的,所以在这个时候其实我们就可以告知客户端让其提前下载好模板。当然大家不要忘记,除了页面之外我们还要有新闻数据,为了实现纯离线化同时也避免新闻数据接口的请求,在列表中还会将每条新闻的详细数据下发下去,保证必备要素的本地化。

如图所示,在列表请求的接口中,服务端会将需要缓存的静态页面地址以及每条新闻对应的新闻数据全部下发给客户端,客户端接收到请求之后会进行模板的下载。

客户端行为

需要的东西下发下去之后剩下的就是客户端进行渲染了。正常来说除了模板页面之外,服务端还需要下载其他相关的静态资源,然后启动一个 HTTP 服务将页面和资源文件进行关联,关联之后将数据注入到页面之后打开页面。但这对客户端的要求就非常多了,为了将客户端的工作量降低,我们将所有需要使用的静态资源通过编译内联到 HTML 文件内,客户端通过字符串拼接的形式将数据注入到页面的全局变量中。

如图所示所有静态资源都被标记了 inline 属性,我们的编译工具在读取到这个属性后会将当前资源给内联到 HTML 中。同时大家注意到该模板不是以 <html> 开头的,而是有一些截断。这是为了给客户端提供注入数据空间,客户端通过模板字符串拼接的形式将新闻数据注入到全局变量中最终完成整个新闻页面的获取。前端代码中则直接使用 __INJECT_DATA_FROM_CLIENT_DONT_MODIFY__ 全局变量获取注入的数据。

页面的更新

上面就是一套完整的本地化下发并打开的流程了,总的来讲就分为四步:

  1. 前端将页面处理成真·单页应用
  2. 服务端在列表时将数据和本地化模板下载地址通过接口下发给客户端
  3. 客户端获取到模板下载地址后进行下载
  4. 当用户打开新闻的时候客户端将数据和模板进行拼接打开即可

但是只要有资源的分发就会涉及到资源的同步更新问题,我们的本地化模板也是一样。在我们的线上更新的时候如何让客户端知晓并触发更新行为,也是我们需要去考虑的问题。实际上大家在前两张截图中可以看到,为了解决这个问题,我们是在服务端下发的接口中还增加了一个 version 字段,用来标记当前 HTML 的版本。而当前端进行代码发布的时候,我们的发布系统会有一个类似 npm 的 postpublish 的钩子,利用这个钩子我们告诉服务端发布成功更新版本号。最后,当客户端接收到新的版本号的时候则会重新下载新的模板,完成一次本地模板的更新。

跨域问题

在前端页面中,Cookie 和 LocalStorage 等大量的特性是和域名相关的,而不巧的是我们的页面中都有使用,所以跨域也是我们需要考虑到的问题。我们知道,本质上此种方案下客户端相当于使用 WebView 打开了一个本地页面,而在 Android 系统中 WebView 打开本地页面的话有三种方法:

  • loadUrl:本质上使用 file:///temp.html 的形式打开一个本地文件 URL
  • loadData:和 loadUrl 类型,好的地方在于不需要写成文件,可以直接加载页面字符串,不过此时加载完之后页面的 URL 是 about:blank
  • loadDataWithBaseURL:和 loadData 类似,好的地方在于提供了参数能够设置当前 URL 地址

从描述中可以看到,很明显最后一种 loadDataWithBaseURL 才是我们需要的。客户端通过这个方法加载,设置当前页面的 URL 为真实线上 URL,对于前端来说基本上就和线上环境无异了,本地化和线上 Cookie 和 LocalStorage 的共享都没有问题。不过这里需要注意,第一个参数 baseUrl 仅能管住当前页面,如果页面做了 history.pushState() 等前进后退操作的话当前页面地址又会变成 about:blank,此时需要再设置最后一个参数 historyUrl 才行。

后记

本文给大家讲述了实现本地化离线阅读的方案。除了以上列举的问题,我们还碰到了一些细微的问题。例如我们发现在网络不好的情况下客户端可能会下载模板失败缓存了不完整的代码,所以我们增加了模板的 md5 值一并下发给客户端用来校验模板是否下载完全。又如上文说了模板的更新,实际上内容也会有更新,特别是一些新闻的实时性会有比较高的要求,为了解决这个问题,我们会在页面打开后再次去检查一下文章的状态,如果发生变量会切换至线上版本用来规避这个问题。除了这些之外我们还做了完备的云控后退方案,能在方案出问题的时候完美回退到普通版本。

其实大家可以看到,本地化只是我们在特定场景下决绝性能问题的一种特定思路。它并不是使用于所有的场景,所以我在文章开头也特别强调了一下我们的应用场景方便大家去理解。但是我们只要理解这种方案的精髓,我相信在其它的一些特定场合总能发挥它的威力。

[无聊的App]熊宝儿歌故事QuickApp

熊宝儿歌故事QuickApp

熊宝儿歌故事基于快应用技术开发,且小程序版已经上线欢迎大家体验,由于快应用推出时间不久,网络上关于快应用的资料少之又少,本人也因公司需求进行开发,一路上踩坑无数,并集成友盟统计,完全遵循正式项目,今想把自己的一些经验分享出来,让大家少走弯路,如有错误和意见欢迎大家提出,共同进步。

banner.png

首页

音乐/视频

信息流

以下所有 API 均由产品公司自身提供,本人皆从网络获取。获取与共享之行为或有侵犯产品权益的嫌疑。若被告知需停止共享与使用,本人会及时删除此页面与整个项目。请您暸解相关情况,并遵守产品协议。

为了方便大家学习和测试,且保证原公司利益,本项目采用easy-mock模拟后台数据。

项目介绍

熊宝儿歌故事基于快应用技术开发,分音乐、视频、育儿信息流三大模块,音乐模块相对复杂,包含套完整的音乐播放器逻辑,且快应用坑较多,所以实现起来难度加大,其他模块均为信息流方式呈现,包含常用的上拉刷新,下拉加载逻辑。

目录结构


├── sign                      rpk包签名模块
│   └── debug                 调试环境
│       ├── certificate.pem   证书文件
│       └── private.pem       私钥文件
├── src
│   ├── api                   请求接口
│   ├── common                公用的资源和组件文件
│   ├── components            组件文件
│   ├── pages                  页面目录
│   |   └── index.ux          页面文件,可自定义页面名称
│   ├── app.ux              APP文件,可引入公共脚本,暴露公共数据和方法等
│   └── manifest.json         项目配置文件,配置应用图标、页面路由等
└── package.json              定义项目需要的各种模块及配置信息

项目安装

    git clone [email protected]:lishuaixingNewBee/babySongQuickapp.git
    npm install
    打开两个终端窗口分别执行:
    npm run watch
    npm run server
    如果开发者在后续操作中遇到报错Cannot find module '.../node_modules/hap-tools/webpack.config.js',请运行一次
    hap update —force

编译打包

特别注意:如果是需要发布到应用市场是安卓应用一样需要签名文件
npm run build
编译后的工程目录在/build
生成的应用路径为/dist/.rpk

增加release签名

1、创建私钥:
通过openssl命令等工具生成签名文件private.pem、certificate.pem,如:
openssl req -newkey rsa:2048 -nodes -keyout private.pem -x509 -days 3650 -out certificate.pem
(密钥长度,1024觉得不够安全的话可以用2048,但是代价也相应增大)
在工程的sign目录下创建release目录,将私钥文件private.pem和证书文件certificate.pem拷贝进去

Country Name (2 letter code) [XX]:CN   #国家代码(**)
State or Province Name (full name) []:BeiJing   #省(北京)
Locality Name (eg, city) [Default City]:BeiJing   #市(北京)
Organization Name (eg, company) [Default Company Ltd]:tact  #公司名称
Organizational Unit Name (eg, section) []:   #可不填
Common Name (eg, your name or your server's hostname) []: #可不填
Email Address []:[email protected]  #邮箱
 
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:   #可不填
An optional company name []:   #可不填
在工程的sign目录下创建release目录,将私钥文件private.pem和证书文件crtificate.pem拷贝进去

发布程序包

发布程序包前需要增加release签名,然后在工程的根目录下运行
npm run release
生成的应用路径为/dist/.signed.rpk
如果需要临时使用debug签名,可以使用
npm run release -- --debug
注意: debug签名由于是公开的,安全性无法保证,一定不要使用debug签名签发正式上线的应用,一定要保存好你的签名文件。

开发中常见问题

如一些基本问题,组件传参啥的我就不仔细讲了,大家去看文档就好了。我主要说一些坑吧。

  1. 关于样式组件问题
    - 快应用默认flex布局,所以咱们之前float,position啥的都不好使。
    - background-image 不支持网络路径
    - 不支持 box-shadow属性(很多样式都不支持),想实现的话用背景图去实现
    - 不支持 webfont,据说后期会加上。
    - svg的图片在华为应用平台有兼容问题。
    - 华为应用平台对动画有兼容性问题,下一版才会解决
    - tabs内不能再嵌套tabs,如有此类需求,请参考教程第一部分div组件模拟选项卡
    - 要实现z-index的图层效果请使用stack
    - swiper不支持 滑动方向改变
    - list-item组件dom结构不一致时,一定要给type="***"不同命名来区别,否则也会闪退,且组件的if改为show否则,会改变dom结构。
    暂时采用<list-item type="{{index}}">解决
    
  2. 关于框架API
 - 父子组件prop传参数,如果属性名称使用驼峰定义,如:prop2Object,那么在外部传递数据时请使用-连接,如:prop2-object
 - slider 组件只有滑动结束后end才有回调,进行中没有回调,改变value值也会触发change事件,无法判断change是人为滑动触发,还是改变数据触发的
 - 没有提供获取音频播放状态的接口。
 - 组件没有onDestroy钩子,用if不会被触发。
 - 无selectionchange 选中文本改变和光标移动时触发(据说1030会加上)。
 - 有没有类似 touchstart  和 touchend的事件(确定1030会加上)。
 - this.$forceUpdate()相当于vue的this.$forceUpdate(),实例重新渲染(快应用解决一些页面渲染问题的未知bug)。
  1. 重新封装fetch接口
import nativeFetch from '@system.fetch';
import prompt from '@system.prompt';
const BASE_URL = `https://xxxxxx`;
class http {
  static async request(method, Aurl, data) {
    let url = Aurl;
    const strRegex = '(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
    var reg = new RegExp(strRegex);
    if (!reg.test(url)) {
      url = BASE_URL + url;
    }
    console.warn(`请求地址:${url}`);
    const res = await this.fetch(method, url, data)
    if (this.isSuccess(res)) {
      return JSON.parse(res.data);
    } else {
      console.warn('请求错误');
      throw this.requestException(res);
    }
  }
  static fetch(method, url, data) {
    return new Promise((resolve, reject) => {
      nativeFetch.fetch({
        method,
        url,
        data,
        success: resolve,
        fail: reject
      })
    })
  }
  static isSuccess(res) {
    const quickappCode = res.code;
    // 快应用请求错误
    if (quickappCode !== 200) {
      return false;
    }
    const quickappData = JSON.parse(res.data)
    return !(quickappData && quickappData.status !== 0);
  }
  /*异常*/
  static requestException(res) {
    const error = {};
    error.statusCode = res.code;
    const quickappData = res.data;
    if (quickappData) {
      const serverData = JSON.parse(res.data)
      if (serverData) {
        error.serverCode = serverData.status;
        error.message = serverData.msg;
        error.serverData = serverData.data;
      }
    }
    return error;
  }
  static get(url, data) {
    return this.request('GET', url, data);
  }

  static put(url, data) {
    return this.request('PUT', url, data);
  }

  static post(url, data) {
    return this.request('POST', url, data);
  }

  static patch(url, data) {
    return this.request('PATCH', url, data);
  }

  static delete(url, data) {
    return this.request('DELETE', url, data);
  }
}
// 全局注册
// export default base
const injectRef = Object.getPrototypeOf(global) || global
injectRef.$http = http

○ 更新记录

2018.11.23

    -  求小伙伴一起维护项目

[无聊的App]狗蛋TV

狗蛋TV是基于微信小程序开发的一款App。gordanLee每天都会推荐一首歌、一篇文章、一段短视频,每天用十分钟的细碎时光,点燃内心的光明。目前分为音乐,短视频,影评三个模块。

banner.png

引导页

音乐页

短视频页

影评页

搜索页

以下所有 API 均由产品公司自身提供,本人皆从网络获取。获取与共享之行为或有侵犯产品权益的嫌疑。若被告知需停止共享与使用,本人会及时删除此页面与整个项目。请您暸解相关情况,并遵守产品协议。

为了方便大家学习和测试,我们提供了https的接口供大家使用,且用且珍惜。请在微信开发设置中加入request合法域名,或者在开发设置中勾选——不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书。

感谢与支持

    -   狗蛋TVapi: https://api.gordantv.top
    -   豆瓣api: https://api.douban.com
    -   QQ音乐api: https://y.qq.com

项目介绍

狗蛋TV是基于微信小程序+ES6进行开发,能同时运行在Android、iOS环境下。涵盖了音乐、短视频、影评三个版块。

  • 开屏引导图
    1. 调用微信wx.onAccelerometerChange重力感应设备API,实现水波荡漾。
    2. 调用wx.getUserInfo获取用户头像。
  • 工具类
    1. 用Promise封装wx.request(),简化代码结构:
const $get = (url, data) => {
  return new Promise((resolve, reject) => {
    wx.request({
      url,
      data,
      header: { 'Content-Type': 'json' },
      success: resolve,
      fail: reject
    })
  })
}
  1. 电影评分实现
const convertToStarsArray = (average) => {
  const LENGTH = 5;
  const CLS_ON = 'on'; // 全星
  const CLS_HALF = 'half'; // 半星
  const CLS_OFF = 'off'; // 无星
  let result = [];
  let score = Math.round(average) / 2;
  let hasDecimal = score % 1 !== 0
  let integer = Math.floor(score)
  for (let i = 0; i < integer; i++) {
    result.push(CLS_ON)
  }
  if (hasDecimal) {
    result.push(CLS_HALF)
  }
  while (result.length < LENGTH) {
    result.push(CLS_OFF)
  }
  return result;
}
  • 小程序内部组件实现上拉刷新,下拉加载
    方法一:scroll-view 组件
    方法二:onPullDownRefresh和onReachBottom方法实现小程序下拉加载和上拉刷新
  • 阅读模块

    1. 微信小程序使用wxParse解析html
    项目中遇到在微信小程序里需要显示音乐文章的内容,文章内容是通过接口读取的服
    务器中的富文本内容,是html格式的,小程序默认是不支持html格式的内容显示的,
    那我们需要显示html内容的时候,就可以通过wxParse来实现。
    

项目安装

    git clone [email protected]:lishuaixingNewBee/gordanTv.git

目录结构


|--- utils & Public Function              通用函数
|--- components & components Public View  components和template模板
|--- images & Img Resources               图片资源
|--- pages & View Dir                     页面

○ 更新记录

2018.5.17

    -   解决微信小程序中Date.parse()获取时间戳IOS不兼容的问题(IOS为NaN的问题)

2018.5.25

    -   微信废弃 获取 wx.getUserInfo 接口后续将不再出现授权弹窗,升级为 <button class="getUserInfo_btn" open-type="getUserInfo" lang="zh_CN" bindgetuserinfo="onGotUserInfo">允许</button>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.