关于Web静态资源缓存自动更新的思考与实践

关于Web静态资源缓存自动更新的思考与实践

2016/04/06 · 基础技术 ·
静态资源

本文作者: 伯乐在线 –
Natumsol
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

前言

对于前端工程化而言,静态资源的缓存与更新一直是一个比较大的问题,各大公司也推出了各自的解决方案,如百度的FIS工具集。如果没有解决好这个问题,不仅会给用户造成糟糕的用户体验,而且还会给开发和调试带了很多不必要的麻烦。关于如何自动实现缓存更新,以下是自己的一点心得和体会。

解决

客户端优化

总结起来,大体优化思路就是:缓存/预加载/并行,缓存一切网络请求,尽量在用户打开之前就加载好所有内容,能并行做的事不串行做。这里有些优化手段需要做好一整套工具和流程支持,需要跟开发效率权衡,视实际需求优化。

作者:张云龙

静态资源发布的痛点

我们知道,缓存对于前端性能的优化是十分重要的,在正式发布系统的时候,对于那些不经常变动的静态资源比如各种JS工具库、CSS文件、背景图片等等我们会设置一个比较大的缓存过期时间(max-age),当用户再次访问这个页面的时候就可以直接利用缓存而不是重新从服务器获取,这样不仅可以减轻服务端的压力,还可以节约网络传输的流量,同时用户体验也更好(用户打开页面更快了)。这样看起来很完美,你好我好大家都好,but,理想是美好的,现实是残酷的,假设存在这样一个浏览器,强制缓存静态资源还不给你清除缓存的机会(微信,说的就是你!),该怎么办?即使你的服务端已更新,文件的Etag值已变化,但是微信就是不给你更新文件…请允许我做一个悲伤的表情…

对于这个问题,我们很自然的想法是在每次发布新版本的时候给所有静态资源的请求后面加上一个版本参数或时间戳,类似于/js/indx.js?ver=1.0.1,但是这样存在两个问题:

  1. 微信对于加参数的静态资源还是优先使用缓存版本(实际测试的情况是这样的)。
  2. 假如这样是可行的,那么对于没有变更的静态资源也会重新从服务器获取而不是读取缓存,没有充分利用缓存。

那么有没有一种方法可以自动分辨出哪个文件发生了变化并让客户端主动更新呢?答案是肯定的。我们知道一个文件的MD5可以唯一标识一个文件。若文件发生了变化,文件的指纹值MD5也随之变化。利用这个特性我们就可以标识出哪个静态资源发生了变化,并让客户端主动更新。

分析

5.离线包数据返回,否则走 HTTP 协议缓存逻辑。

上述几种方案策略也可以混着使用,看业务需求。

不过整篇回答没有讲解到具体的解决方案实现思路,只是介绍了前端在工程化方向的思考,答案本身是可用的,了解rails的人也可以把此答案当做是对rails中assets
pipeline设计原理的分析。

如何解决?

经过前文的介绍,我们知道了可以利用文件的指纹值来标识需要客户端主动更新的文件,但是如何实现呢?经过自己的思考和调研后,大致思路为:

  1. 在每次发布之前,利用Gulp对所有的静态资源进行预处理,重命名为原文件名 + 文件MD5值 + 文件后缀名的形式。比如index.js重命名为index-c6c9492ce6.js
  2. 生成一份manifest,标明了预处理前后文件之间的对应关系.manifest文件的样子为:
JavaScript

{ "index.js": "index-c6c9492ce6.js", "lib/jQuery/jQuery.js":
"lib/jQuery/jQuery-683c73084c.js", "require.js":
"require-c8e8015f8d.js", "style.css": "style-125d3a3f82.css",
"tools.js": "tools-5666ee48e9.js" }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4b6669294327058473-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b6669294327058473-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f4b6669294327058473-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b6669294327058473-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f4b6669294327058473-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b6669294327058473-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f4b6669294327058473-7">
7
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4b6669294327058473-1" class="crayon-line">
{
</div>
<div id="crayon-5b8f4b6669294327058473-2" class="crayon-line crayon-striped-line">
  &quot;index.js&quot;: &quot;index-c6c9492ce6.js&quot;,
</div>
<div id="crayon-5b8f4b6669294327058473-3" class="crayon-line">
  &quot;lib/jQuery/jQuery.js&quot;: &quot;lib/jQuery/jQuery-683c73084c.js&quot;,
</div>
<div id="crayon-5b8f4b6669294327058473-4" class="crayon-line crayon-striped-line">
  &quot;require.js&quot;: &quot;require-c8e8015f8d.js&quot;,
</div>
<div id="crayon-5b8f4b6669294327058473-5" class="crayon-line">
  &quot;style.css&quot;: &quot;style-125d3a3f82.css&quot;,
</div>
<div id="crayon-5b8f4b6669294327058473-6" class="crayon-line crayon-striped-line">
  &quot;tools.js&quot;: &quot;tools-5666ee48e9.js&quot;
</div>
<div id="crayon-5b8f4b6669294327058473-7" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 在渲染视图模版的时候,根据manifest,将预处理前的静态资置换为预处理后的静态资源。
  2. 如果在浏览器端用到了模块加载器(这里以实现了AMD标准的requireJS为例),在每次发布的时候需要根据manifest对模块进行mapping,将配置文件以内联JS的形式写入到模版页面里面,类似于:
JavaScript

&lt;script&gt; requirejs.config({ "baseUrl": "/js", "map": { "*": {
"index": "index-c6c9492ce6", "jquery":
"lib/jQuery/jQuery-683c73084c", "require": "require-c8e8015f8d",
"tools": "tools-5666ee48e9" } } }); &lt;/script&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b666929d715705975-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b666929d715705975-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b666929d715705975-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b666929d715705975-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b666929d715705975-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f4b666929d715705975-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f4b666929d715705975-13">
13
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f4b666929d715705975-1" class="crayon-line">
&lt;script&gt;
</div>
<div id="crayon-5b8f4b666929d715705975-2" class="crayon-line crayon-striped-line">
requirejs.config({
</div>
<div id="crayon-5b8f4b666929d715705975-3" class="crayon-line">
    &quot;baseUrl&quot;: &quot;/js&quot;,
</div>
<div id="crayon-5b8f4b666929d715705975-4" class="crayon-line crayon-striped-line">
    &quot;map&quot;: {
</div>
<div id="crayon-5b8f4b666929d715705975-5" class="crayon-line">
        &quot;*&quot;: {
</div>
<div id="crayon-5b8f4b666929d715705975-6" class="crayon-line crayon-striped-line">
            &quot;index&quot;: &quot;index-c6c9492ce6&quot;,
</div>
<div id="crayon-5b8f4b666929d715705975-7" class="crayon-line">
            &quot;jquery&quot;: &quot;lib/jQuery/jQuery-683c73084c&quot;,
</div>
<div id="crayon-5b8f4b666929d715705975-8" class="crayon-line crayon-striped-line">
            &quot;require&quot;: &quot;require-c8e8015f8d&quot;,
</div>
<div id="crayon-5b8f4b666929d715705975-9" class="crayon-line">
            &quot;tools&quot;: &quot;tools-5666ee48e9&quot;
</div>
<div id="crayon-5b8f4b666929d715705975-10" class="crayon-line crayon-striped-line">
        }
</div>
<div id="crayon-5b8f4b666929d715705975-11" class="crayon-line">
    }
</div>
<div id="crayon-5b8f4b666929d715705975-12" class="crayon-line crayon-striped-line">
});
</div>
<div id="crayon-5b8f4b666929d715705975-13" class="crayon-line">
&lt;/script&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

【编辑推荐】

2.离线包核心文件和页面动态的图片资源文件缓存分离,可以更方便地管理缓存,离线包也可以整体提前加载进内存,减少磁盘
IO 耗时。

这样看起来已经比较完美了,HTML
文件在用客户端的策略缓存,其余资源和数据沿用上述前端的缓存方式,这样一个
H5 页面第二次访问从 HTML 到 JS/CSS/Image
资源,再到数据,都可以直接从本地读取,无需等待网络请求,同时又能保持尽可能的实时更新,解决了缓存问题,大大提升
H5 页面首屏启动速度。

先部署资源,再部署页面:在部署时间间隔之内,有旧版本资源本地缓存的用户访问网站,由于请求的页面是旧版本的,资源引用没有改变,浏览器将直接使用本地缓存,这种情况下页面展现正常;但没有本地缓存或者缓存过期的用户访问网站,就会出现旧版本页面加载新版本资源的情况,导致页面执行错误,但当页面完成部署,这部分用户再次访问页面又会恢复正常了。

测试

为了验证可行性,自己做了个demo,代码托管在Github。经测试,可以完美的解决之前提出的问题。

  1. 首次载入页面
    永利酒店赌场 1
  2. 更改index.js, 刷新页面
    永利酒店赌场 2

我们发现,只有index.js在更改后被主动更新了,其余的静态资源均是直接利用的缓存!。

DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP
才能建立连接。将域名解析为 IP 的这个系统就是 DNS。DNS
查询结果通常会被缓存一段时间,但第一次访问或者缓存失效时,还是可能耗费几十到几百毫秒;

一般页面在 dom
渲染后能显示雏形,在这之前用户看到的都是白屏,等到下载渲染图片后整个页面才完整显示,首屏秒开优化就是要减少这个过程的耗时。

2.客户端根据配置表,在自定义时机去把离线包拉下来,做解压/解密/校验等工作。

什么东西与文件内容相关呢?我们会很自然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。好了,我们把url改成带摘要信息的:

关于作者:Natumsol

永利酒店赌场 3

阿里巴巴 前端工程师
个人主页 ·
我的文章 ·
5 ·
   

永利酒店赌场 4

不得不说这几年 WEB 技术一直在突飞猛进,爆炸式发展。昨天还觉得 HTTP/2
很遥远,今天已经遍地都是了。对于新鲜事物,有些人不愿意接受,觉得好端端为什么又要折腾;有些人会盲目崇拜,认为它是能拯救一切的救世主。HTTP/2
究竟会给前端带来什么,什么都不是?还是像某些人说的「让前端那些优化小伎俩直接退休」?我打算通过写一系列文章来尝试回答这个问题,今天是第一篇。

无论是 iOS 还是 Android,本地 webview
初始化都要不少时间,可以预先初始化好 webview。这里分两种预加载:

网路和存储接口如果使用 webkit 的 ajax 和 localStorage
会有不少限制,难以优化,可以在客户端提供这些接口给
JS,客户端可以在网络请求上做像 DNS
预解析/IP直连/长连接/并行请求等更细致的优化,存储也使用客户端接口也能做读写并发/用户隔离等针对性优化。

好的,上面一坨分析想说的就是:先部署谁都不成!都会导致部署过程中发生页面错乱的问题。所以,访问量不大的项目,可以让研发同学苦逼一把,等到半夜偷偷上线,先上静态资源,再部署页面,看起来问题少一些。

后记

关于前端性能优化,缓存一直是浓墨重彩的一笔。如果利用好缓存控制,不仅能提高用户体验,减少服务端流量压力,而且对于前端工程化的推进也是很有帮助的。随着web系统的业务和功能的扩大,维护前端的任务将变得越来越繁重,按照历史规律,当一件事变得越来越繁重的时候,工程化是其唯一的出路。现在的前端还很年轻,工程化的概念提出来不久,但我相信,在各大互联网公司的前端们积极推动下,前端工程化必将成为业界标配。

打赏支持我写出更多好文章,谢谢!

打赏作者

HTTP/1

2.也可以是如果本地有旧包,用户本次就直接使用旧包,如果没有再同步阻塞等待,这种会导致更新不及时,无法确保用户使用最新版本。

1.可以配置一个预加载列表,在APP启动或某些时机时提前去请求,这个预加载列表需要包含所需
H5
模块的页面和资源,还需要考虑到一个H5模块有多个页面的情况,这个列表可能会很大,也需要工具生成和管理这个预加载列表。

永利酒店赌场 5

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

永利酒店赌场 6
永利酒店赌场 7

1 赞 4 收藏
评论

我们先来考虑资源外链的情况。通常,外链资源都会部署在 CDN
上,这样用户就可以从离自己最近的节点上获取数据。一般文本文件都会采用
gzip
压缩,实际传输大小是文件大小的几分之一。服务端托管静态资源的效率通常非常高,服务端处理时间几乎可以忽略。在忽略网络因素、传输大小以及服务端处理时间之后,用户何时能加载完外链资源,很大程度上取决于请求何时能发出去,这主要受下面三个因素影响:

接着轮到客户端出场了,桌面时代受限于浏览器,H5
页面无法做更多的优化,现在 H5 页面是内嵌在客户端 APP
上,客户端有更多的权限,于是客户端上可以超出浏览器的范围,做更多的优化。

为什么打开一个H5页面会有一段空白时间?因为他做了:

在我的印象中,facebook是这个领域的鼻祖,有兴趣、有梯子的同学可以去看看facebook的页面源代码,体会一下什么叫工程化。

HTTP/2

无法防劫持:若 HTML
页面被运营商或其他第三方劫持,将长时间缓存劫持的页面。

离线包方案在缓存上已经做得差不多了,还可以再配上一些细节优化:

看看那个a.css的请求吧,如果每次用户访问页面都要加载,是不是很影响性能,很浪费带宽啊,我们希望最好这样:

很早之前,就有网站开始针对第一次访问的用户将资源内联,并在页面加载完之后异步加载这些资源的外链版本,同时记录一个
Cookie
标记表示用户来过。用户再次访问这个页面时,服务端就可以输出只有外链版本的页面,减小体积。

json 数据的缓存可以用 localStorage
缓存请求下来的数据,可以在首次显示时先用本地数据,再请求更新,这都由前端
JS 控制。

1.首次预加载:在一个进程内首次初始化 webview
与第二次初始化不同,首次会比第二次慢很多。原因预计是 webview
首次初始化后,即使 webview 已经释放,但一些多 webview
共用的全局服务或资源对象仍没有释放,第二次初始化时不需要再生成这些对象从而变快。我们可以在
APP 启动时预先初始化一个 webview 然后释放,这样等用户真正走到 H5
模块去加载 webview时就变快了。

要解释优化与工程的结合处理思路,又会扯出一堆有关模块化开发、资源加载、请求合并、前端框架等等的工程问题,以上只是开了个头,解决方案才是精髓,但要说的太多太多,有空再慢慢展开吧。或者大家可以去我的blog看其中的一些拆解:fouber/blog
·
GitHub

由于 Cookie
内容需要尽可能的少,所以一般只存总的版本号。这会导致页面任何一处资源变动,都会改变总版本号,进而忽略客户端所有
localStorage 缓存。要解决这个问题可以继续改进我们的方案:Cookie
中只存放用户唯一标识,用户和资源对应关系存在服务端。服务端收到请求后根据用户标识,计算出哪些资源需要更新,从而输出更有针对性的
HTML 文档。

初始化 webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求
js/css 资源 -> dom 渲染 -> 解析 JS 执行 -> JS 请求数据 ->
解析渲染 -> 下载渲染图片

1.在客户端拦截请求,首次请求 HTML
文件后缓存数据,第二次不发请求,直接使用缓存数据。

永利酒店赌场 8

这个方案除了有点浪费流量之外(一份资源,内联外链加载了两次),基本上能达到更快加载重要资源的效果。但是在流量更加宝贵的移动端,我们需要继续改进这个方案。

磁盘 IO 无法控制,无法从磁盘预加载数据到内存。

理想情况下离线包的方案第一次打开时所有 HTML/JS/CSS
都使用本地缓存,无需等待网络请求,但页面上的用户数据还是需要实时拉,这里可以做个优化,在
webview 初始化的同时并行去请求数据,webview
初始化是需要一些时间的,这段时间没有任何网络请求,在这个时机并行请求可以节省不少时间。

在评论中,

提出问题

1.降低请求量:合并资源,减少 HTTP 请求数,minify / gzip
压缩,webP,lazyLoad。

每个包都会使用相同的 JS 框架和 CSS
全局样式,这些资源重复在每一个离线包出现太浪费,可以做一个公共资源包提供这些全局文件。

好了,当我要更新静态资源的时候,同时也会更新html中的引用吧,就好像这样:

今年二月份,Google 宣布将在 16 年初放弃对 SPDY 的支持,随后 Google
自家支持 SPDY 协议的服务都切到了 HTTP/2。今年 5 月 14 日,HTTP/2 以 RFC
7540 正式发布。目前,浏览器方面,Chrome 40+ 和 Firefox 36+ 都正式支持了
HTTP/2;服务器方面,著名的 Nginx 表示会在今年底正式支持 HTTP/2。

更新体验差:后台 HTML/JS/CSS 更新时全量下载,数据量大,弱网下载耗时长。

这里讨论了 H5 页面首屏启动时间的优化,上述优化过后,基本上耗时只剩
webview
本身的启动/渲染机制问题了,这个问题跟后续的响应流畅度的问题一起属于另一个优化范围,就是类
RN / Weex 这样的方案,有机会再探讨。

这个示例也可以用于和assets
pipeline做比较。fis没有assets的目录规范约束,而且可以以独立工具的方式组合各种前端开发语言(coffee、less、sass/scss、stylus、markdown、jade、ejs、handlebars等等你能想到的),并与其他后端开发语言结合。

浏览器阻塞(Stalled):浏览器会因为一些原因阻塞请求。例如在 rfc2616
中规定浏览器对于一个域名,同时只能有 2 个连接(HTTP/1.1
的修订版中去掉了这个限制,详见
rfc7230,因为后来浏览器实际上都放宽了限制),超过浏览器最大连接数限制,后续请求就会被阻塞。再例如现代浏览器在加载同一域名多个
HTTPS 资源时,会有意等第一个 TLS 连接建立完成再请求其他资源;

3.还可以对离线包做一个线上版本,离线包里的文件在服务端有一一对应的访问地址,在本地没有离线包时,直接访问对应的线上地址,跟传统打开一个在线页面一样,这种体验相对等待下载整个离线包较好,也能保证用户访问到最新。

接着轮到客户端出场了,桌面时代受限于浏览器,H5
页面无法做更多的优化,现在 H5 页面是内嵌在客户端 APP
上,客户端有更多的权限,于是客户端上可以超出浏览器的范围,做更多的优化。

好了,目前我们快速的学习了一下前端工程中关于静态资源缓存要面临的优化和部署问题,新的问题又来了:这™让工程师怎么写码啊!!!

对于 HTTP/2 来说,要解决前面这个问题简直就太容易了,开启「Server
Push」即可。HTTP/2
的多路复用特性,使得可以在一个连接上同时打开多个流,双向传输数据。Server
Push,意味着服务端可以在发送页面 HTML
时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。另外,服务端主动推送的资源不是被内联在页面里,它们有自己独立的
URL,可以被浏览器缓存,当然也可以给其他页面使用。

上述方案似乎已完整解决缓存问题,但实际上还有很多问题:

Fallback

永利酒店赌场 9

我们知道,一个页面通常由一个 HTML
文档和多个资源组成。有一些很重要的资源,例如头部的 CSS、关键的
JS,如果迟迟没有加载完,会阻塞页面渲染或导致用户无法交互,体验很差。如何让重要的资源更快加载完是我本文要讨论的问题。

1.简单的方案是如果本地离线包没有或不是最新,就同步阻塞等待下载最新离线包。这种用户打开的体验更差了,因为离线包体积相对较大。

无论是 iOS 还是 Android,本地 webview
初始化都要不少时间,可以预先初始化好 webview。这里分两种预加载:

另外,也不要觉得这些是运维或者后端工程师要解决的问题。如果由其他角色来解决,大家总是把自己不关心的问题丢给别人,那么前端工程师的开发过程将受到极大的限制,这种情况甚至在某些大公司都不少见!

再来看看资源内联的情况。把 CSS、JS 文件内容直接内联在 HTML
中的方案,毫无疑问会在用户第一次访问时有速度优势。但通常我们很少缓存
HTML
页面,这种方案会导致内联的资源没办法利用浏览器缓存,后续每次访问都是一种浪费。

2.客户端根据配置表,在自定义时机去把离线包拉下来,做解压/解密/校验等工作。

2.客户端可以接管所有请求的缓存,不走 webview
默认缓存逻辑,自行实现缓存机制,可以分缓存优先级以及缓存预加载。

用F.I.S包装了一个小工具,完整实现整个回答所说的最佳部署方案,并提供了源码对照,可以感受一下项目源码和部署代码的对照。

这套方案要投入实际使用,要处理一系列异常情况,例如 JS / Cookie /
localStorage 被禁用;localStorage 被写满;localStorage
内容损坏或丢失等等。考虑成本和实际收益,推荐只在移动项目中使用这种方案。

永利酒店赌场,其中对首屏启动速度影响最大的就是网络请求,所以优化的重点就是缓存,这里着重说一下前端对请求的缓存策略。我们再细分一下,分成
HTML 的缓存,JS/CSS/image 资源的缓存,以及 json 数据的缓存。

2.也可以是如果本地有旧包,用户本次就直接使用旧包,如果没有再同步阻塞等待,这种会导致更新不及时,无法确保用户使用最新版本。

来源:知乎

服务端可以主动推送,客户端也有权利选择接收与否。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送
RST_STREAM 帧来拒收。

每个包都会使用相同的 JS 框架和 CSS
全局样式,这些资源重复在每一个离线包出现太浪费,可以做一个公共资源包提供这些全局文件。

最后

配置超长时间的本地缓存                —— 节省带宽,提高性能

如果标记不存在或者版本不匹配,就将资源内联输出,并提供当前版本标记。页面执行时,会把内联资源存入
localStorage,并将资源版本标记存入 Cookie;

1.可以预先下载整个离线包,只需要按业务模块配置,不需要按文件配置,离线包包含业务模块相关的所有页面,可以一次性预加载。

2.什么时候去请求更新?这个更新请求可以客户端自由控制策略,可以在使用本地缓存打开本地页面后再在后台发起请求询问更新缓存,下次打开时生效;也可以在
APP 启动时或某个时机在后台去发起请求预更新,提升用户访问最新代码的几率。

—————————- 我是一条分割线 —————————-

可以看到,HTTP/2 的 Server Push
能够很好地解决「如何让重要资源尽快加载」这个问题,一旦普及开来,可以取代前面介绍过的
HTTP/1 时代优化方案。

从前端优化,到客户端缓存,到离线包,到更多的细节优化,做到上述这些点,H5
页面在启动上差不多可以媲美原生的体验了。

问题

部署项目:fouber/static-resource-digest-project-release ·
GitHub

当然我们一般都会给静态资源设置一个很长时间的缓存头。只要用户不清除浏览器缓存也不刷新,第二次访问我们网页时,静态资源会直接从本地缓存获取,并不产生网络请求;如果用户只是普通刷新而不是强刷,浏览器会在请求头带上协商字段
If-Modified-Since 或 If-None-Match,服务端对没有变化的资源会响应 304
状态码,告知浏览器从本地缓存获取资源。304 请求没有正文,非常小。

3.离线包可以很方便地根据版本做增量更新。

2.直接使用本地缓存:根据协议里的 Cache-Control / Expires
字段去确定多长时间内可以不去发请求询问更新,直接使用本地缓存。

链接:

考虑到移动端浏览器都支持
localStorage,可以将第一次内联引入的资源缓存起来后续使用。缓存更新机制可以通过在
Cookie 中存放版本号来实现。这样,服务端收到请求后,首先要检查 Cookie
头中的版本标记:

2.webview 池:可以用两个或多个 webview 重复使用,而不是每次打开 H5
都新建 webview。不过这种方式要解决页面跳转时清空上一个页面,另外若一个
H5 页面上 JS 出现内存泄漏,就影响到其他页面,在 APP
运行期间都无法释放了。

1.后端使用构建工具把同一个业务模块相关的页面和资源打包成一个文件,同时对文件加密/签名。

然后我们访问页面,看到效果,再查看一下网络请求,200!不错,太™完美了!那么,研发完成。。。。了么?

如果标记匹配,就输出 JavaScript 片段,用来从 localStorage
读取并使用资源;

前端能做的最大限度的缓存策略是:HTML
文件每次都向服务器询问是否有更新,JS/CSS/Image资源文件则不请求更新,直接使用本地缓存。那
JS/CSS
资源文件如何更新?常见做法是在在构建过程中给每个资源文件一个版本号或hash值,若资源文件有更新,版本号和
hash 值变化,这个资源请求的 URL 就变化了,同时对应的 HTML
页面更新,变成请求新的资源URL,资源也就更新了。

这些问题在客户端上都是可以被解决的,只不过有点麻烦,简单描述下:

这里更新一下:

也就是说资源外链的特点是,第一次慢,第二次快。

1.没有预加载:第一次打开的体验很差,所有数据都要从网络请求。

3.可以针对每个 HTML 和资源文件做增量更新,只是实现和管理起来比较麻烦。
4.在客户端使用 httpdns + https 防劫持。

永利酒店赌场 10

建立连接(Initial connection):HTTP 是基于 TCP
协议的,浏览器最快也要在第三次握手时才能捎带 HTTP
请求报文。这个过程通常也要耗费几百毫秒;

2.什么时候去请求更新?这个更新请求可以客户端自由控制策略,可以在使用本地缓存打开本地页面后再在后台发起请求询问更新缓存,下次打开时生效;也可以在
APP 启动时或某个时机在后台去发起请求预更新,提升用户访问最新代码的几率。

1.询问是否有更新:根据 If-Modified-Since / ETag
等协议向后端请求询问是否有更新,没有更新返回304,浏览器使用本地缓存。

下次上线,把链接地址改成新的版本,就更新资源了不是。OK,问题解决了么?!当然没有!大公司的变态又来了,思考这种情况:

离线包

4.拦截网络请求,对于离线包已经有的文件,直接读取

看上图,用文件的摘要信息来对资源文件进行重命名,把摘要信息放到资源文件发布路径中,这样,内容有修改的资源就变成了一个新的文件发布到线上,不会覆盖已有的资源文件。上线过程中,先全量部署静态资源,再灰度部署页面,整个问题就比较完美的解决了。

上面的解决方案实现起来十分繁琐,原因就是各个 HTML
和资源文件很多很分散,管理困难,有个较好的方案可以解决这些问题,就是离线包。

1.没有预加载:第一次打开的体验很差,所有数据都要从网络请求。
2.缓存不可控:缓存的存取由系统 webview
控制,无法控制它的缓存逻辑,带来的问题包括:

更资源发布路径实现非覆盖式发布  —— 平滑升级

最后

这些缓存策略可以实现 JS/CSS
等资源文件以及用户数据的缓存的全缓存,可以做到每次都直接使用本地缓存数据,不用等待网络请求。但
HTML 文件的缓存做不到,对于 HTML 文件,如果把 Expires / max-age
时间设长了,长时间只使用本地缓存,那更新就不及时,如果设短了,每次打开页面都要发网络请求询问是否有更新,再确定是否使用本地资源,一般前端在这里的策略是每次都请求,这在弱网情况下用户感受到的白屏时间仍然会很长。所以
HTML 文件的“缓存”和跟“更新”间存在矛盾。

永利酒店赌场 11

服务端渲染

HTML 缓存

@fleuria@林翔
提到了rails,刚刚去看了一下,确实是完成了以上所说的优化细节,对整个静态资源的管理上的思考于本答案描述的一致。很遗憾我直到今天(2014-10-29)才了解到rails中的assets
pipeline。这里向以上3位同学道歉,原谅我的无知。

本文先不讨论第二点,只讨论第一点,怎样减少白屏时间。对 APP 里的一些使用
H5 实现的功能模块,怎样加快它们的启动速度,让它们启动的体验接近原生。

清理逻辑不可控,缓存空间有限,可能缓存几张大图片后,重要的 HTML/JS/CSS
缓存就被清除了。
磁盘 IO 无法控制,无法从磁盘预加载数据到内存。
更新体验差:后台 HTML/JS/CSS
更新时全量下载,数据量大,弱网下载耗时长。
无法防劫持:若 HTML
页面被运营商或其他第三方劫持,将长时间缓存劫持的页面。

assets
pipeline的设计思想值得独立成工具用于前端工程,fis就当做这样的一个选择吧。

4.渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline。

使用客户端接口

接下来,我想从原理展开讲述,多图,较长,希望能有耐心看完。

1.可以配置一个预加载列表,在APP启动或某些时机时提前去请求,这个预加载列表需要包含所需
H5
模块的页面和资源,还需要考虑到一个H5模块有多个页面的情况,这个列表可能会很大,也需要工具生成和管理这个预加载列表。

初始化webview -> 请求页面 -> 下载数据 -> 解析html ->
请求资源js/css -> dom渲染 -> 解析js执行 -> js请求数据 ->
下载渲染图片

采用内容摘要作为缓存更新依据      —— 精确的缓存控制

使用客户端接口

3.根据配置表,打开某个业务时转接到打开离线包的入口页面。

这个奇葩问题,起源于资源的覆盖式发布,用 待发布资源 覆盖
已发布资源,就有这种问题。解决它也好办,就是实现非覆盖式发布

既然很多问题都是文件分散管理困难引起,而我们这里的使用场景是使用 H5
开发功能模块,那很容易想到把一个个功能模块的所有相关页面和资源打包下发,这个压缩包可以称为功能模块的离线包。使用离线包的方案,可以相对较简单地解决上述几个问题:

2.webview 池:可以用两个或多个 webview 重复使用,而不是每次打开 H5
都新建 webview。不过这种方式要解决页面跳转时清空上一个页面,另外若一个
H5 页面上 JS 出现内存泄漏,就影响到其他页面,在 APP
运行期间都无法释放了。

源码项目:fouber/static-resource-digest-project ·
GitHub

优化方法可以是人为减少 JS
渲染逻辑,也可以是更彻底地,回归到原始,所有内容都由服务端返回的 HTML
决定,无需等待 JS
逻辑,称之为服务端渲染。是否做这种优化视业务情况而定,毕竟这种会带来开发模式变化/流量增大/服务端开销增大这些负面影响。手Q的部分页面就是使用服务端渲染的方式,称为动态直出。

到这里,对于使用 H5
开发功能模块,离线包是一个挺不错的方案了,简单复述一下离线包的方案:

利用304,让浏览器使用本地缓存。但,这样也就够了吗?不成!304叫协商缓存,这玩意还是要和服务器通信一次,我们的优化级别是变态级,所以必须彻底灭掉这个请求,变成这样:

总的来说,就是两种缓存:

前端优化

全套做下来,就是相对比较完整的静态资源缓存控制方案了,而且,还要注意的是,静态资源的缓存控制要求在前端所有静态资源加载的位置都要做这样的处理。是的,所有!什么js、css自不必说,还要包括js、css文件中引用的资源路径,由于涉及到摘要信息,引用资源的摘要信息也会引起引用文件本身的内容改变,从而形成级联的摘要变化,大概示意图就是:

HTML 和 JS/CSS/image 资源都属于静态文件,HTTP
本身提供了缓存协议,浏览器实现了这些协议,可以做到静态文件的缓存。

上述方案似乎已完整解决缓存问题,但实际上还有很多问题:

========================[ 10.29更新 ]========================

离线包更新时,根据版本号后台下发两个版本间的 diff
数据,客户端合并,增量更新。

服务端渲染

建议前端工程师多多关注前端工程领域,也许有人会觉得自己的产品很小,不用这么变态,但很有可能说不定某天你就需要做出这样的改变了。而且,如果我们能把事情做得更极致,为什么不去做呢?

这里讨论了 H5 页面首屏启动时间的优化,上述优化过后,基本上耗时只剩
webview
本身的启动/渲染机制问题了,这个问题跟后续的响应流畅度的问题一起属于另一个优化范围,就是类
RN / Weex 这样的方案,有机会再探讨。

从前端优化,到客户端缓存,到离线包,到更多的细节优化,做到上述这些点,H5
页面在启动上差不多可以媲美原生的体验了。

永利酒店赌场 12

3.缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。

1.降低请求量:合并资源,减少 HTTP 请求数,minify / gzip
压缩,webP,lazyLoad。
2.加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。
3.缓存:HTTP 协议缓存请求,离线缓存
manifest,离线数据缓存localStorage。
4.渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline。

永利酒店赌场 13

问题

优化方法可以是人为减少 JS
渲染逻辑,也可以是更彻底地,回归到原始,所有内容都由服务端返回的 HTML
决定,无需等待 JS
逻辑,称之为服务端渲染。是否做这种优化视业务情况而定,毕竟这种会带来开发模式变化/流量增大/服务端开销增大这些负面影响。手Q的部分页面就是使用服务端渲染的方式,称为动态直出。

永利酒店赌场 14

离线包方案在缓存上已经做得差不多了,还可以再配上一些细节优化:

上面的解决方案实现起来十分繁琐,原因就是各个 HTML
和资源文件很多很分散,管理困难,有个较好的方案可以解决这些问题,就是离线包。

现代互联网企业,为了进一步提升网站性能,会把静态资源和动态网页分集群部署,静态资源会被部署到CDN节点上,网页中引用的资源也会变成对应的部署路径:

到这里,对于使用 H5
开发功能模块,离线包是一个挺不错的方案了,简单复述一下离线包的方案:

上述打开一个页面的过程有很多优化点,包括前端和客户端,常规的前端和后端的性能优化在
PC 时代已经有最佳实践,主要的是:

先部署页面,再部署资源:在二者部署的时间间隔内,如果有用户访问页面,就会在新的页面结构中加载旧的资源,并且把这个旧版本的资源当做新版本缓存起来,其结果就是:用户访问到了一个样式错乱的页面,除非手动刷新,否则在资源缓存过期之前,页面会一直执行错误。

2.直接使用本地缓存:根据协议里的 Cache-Control / Expires
字段去确定多长时间内可以不去发请求询问更新,直接使用本地缓存。

早期 web 页面里,JS 只是负责交互,所有内容都是直接在 HTML 里,到现代 H5
页面,很多内容已经依赖 JS 逻辑去决定渲染什么,例如等待 JS 请求 JSON
数据,再拼接成 HTML 生成 DOM
渲染到页面上,于是页面的渲染展现就要等待这一整个过程,这里有一个耗时,减少这里的耗时也是白屏优化的范围之内。

@陈钢

为什么打开一个 H5 页面会有一长段白屏时间?因为它做了很多事情,大概是:

先接着缓存说,在客户端有更自由的缓存策略,客户端可以拦截 H5
页面的所有请求,由自己管理缓存,针对上述 HTML
文件的“缓存”和“更新”之间的矛盾,我们可以用这样的策略解决:

相关资料:英文版:The Asset
Pipeline,中文版:Asset
Pipeline

网路和存储接口如果使用 webkit 的 ajax 和 localStorage
会有不少限制,难以优化,可以在客户端提供这些接口给
JS,客户端可以在网络请求上做像 DNS
预解析/IP直连/长连接/并行请求等更细致的优化,存储也使用客户端接口也能做读写并发/用户隔离等针对性优化。

具体实现上,首先可以在配置表注明某个离线包需要预加载的 URL,客户端在
webview
初始化同时发起请求,请求由一个管理器管理,请求完成时缓存结果,然后
webview 在初始化完毕后开始请求刚才预加载的
URL,客户端拦截到请求,转接到刚才提到的请求管理器,若预加载已完成就直接返回内容,若未完成则等待。

妈妈,我再也不玩前端了。。。。5555

虽然说 H5
页面性能变好了,但如果没针对性地做一些优化,体验还是很糟糕的,主要两部分体验:
1.页面启动白屏时间:打开一个 H5
页面需要做一系列处理,会有一段白屏时间,体验糟糕。
2.响应流畅度:由于 webkit
的渲染机制,单线程,历史包袱等原因,页面刷新/交互的性能体验不如原生。

预加载数据

重新开启变态模式,我们不难发现,要解决这种问题,必须让url的修改与文件内容关联,也就是说,只有文件内容变化,才会导致相应url的变更,从而实现文件级别的精确缓存控制。

公共资源包

前端能做的最大限度的缓存策略是:HTML
文件每次都向服务器询问是否有更新,JS/CSS/Image资源文件则不请求更新,直接使用本地缓存。那
JS/CSS
资源文件如何更新?常见做法是在在构建过程中给每个资源文件一个版本号或hash值,若资源文件有更新,版本号和
hash 值变化,这个资源请求的 URL 就变化了,同时对应的 HTML
页面更新,变成请求新的资源URL,资源也就更新了。

总之,前端性能优化绝逼是一个工程问题!

1.询问是否有更新:根据 If-Modified-Since / ETag
等协议向后端请求询问是否有更新,没有更新返回304,浏览器使用本地缓存。

预加载 webview

所以,大公司的静态资源优化方案,基本上要实现这么几个东西:

理想情况下离线包的方案第一次打开时所有 HTML/JS/CSS
都使用本地缓存,无需等待网络请求,但页面上的用户数据还是需要实时拉,这里可以做个优化,在
webview 初始化的同时并行去请求数据,webview
初始化是需要一些时间的,这段时间没有任何网络请求,在这个时机并行请求可以节省不少时间。

3.离线包可以很方便地根据版本做增量更新。

页面引用了3个css,而某次上线只改了其中的a.css,如果所有链接都更新版本,就会导致b.css,c.css的缓存也失效,那岂不是又有浪费了?!

清理逻辑不可控,缓存空间有限,可能缓存几张大图片后,重要的 HTML/JS/CSS
缓存就被清除了。

总的来说,就是两种缓存:

部署项目可以理解为线上发布后的结果,可以在部署项目里查看所有资源引用的md5化处理。

2.客户端可以接管所有请求的缓存,不走 webview
默认缓存逻辑,自行实现缓存机制,可以分缓存优先级以及缓存预加载。

2.离线包核心文件和页面动态的图片资源文件缓存分离,可以更方便地管理缓存,离线包也可以整体提前加载进内存,减少磁盘
IO 耗时。

永利酒店赌场 15

3.根据配置表,打开某个业务时转接到打开离线包的入口页面。

3.还可以对离线包做一个线上版本,离线包里的文件在服务端有一一对应的访问地址,在本地没有离线包时,直接访问对应的线上地址,跟传统打开一个在线页面一样,这种体验相对等待下载整个离线包较好,也能保证用户访问到最新。

以上不是我YY的,可以观察 百度 或者 facebook
的页面以及静态资源源代码,查看它们的资源引用路径处理,以及网络请中静态资源的缓存控制部分。再次赞叹facebook的前端工程建设水平,跪舔了。

3.可以针对每个 HTML 和资源文件做增量更新,只是实现和管理起来比较麻烦。

公共资源包

永利酒店赌场 16

2.加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。

离线包

但是,大公司超变态,没有这样的“绝对低峰期”,只有“相对低峰期”。So,为了稳定的服务,还得继续追求极致啊!

预加载数据

客户端优化

========================[ 10.31更新 ]========================

更多优化

json 数据的缓存可以用 localStorage
缓存请求下来的数据,可以在首次显示时先用本地数据,再请求更新,这都由前端
JS 控制。

唉~~~~,让我喘口气

4.离线包以压缩包的方式下发,同时会经过加密和校验,运营商和第三方无法对其劫持篡改。

如果用户访问某个离线包模块时,这个离线包还没有下载,或配置表检测到已有新版本但本地是旧版本的情况如何处理?几种方案:

永利酒店赌场 17

另外上述讨论的是针对功能模块类的 H5 页面秒开的优化方案,客户端 APP
上除了功能模块,其他一些像营销活动/外部接入的 H5
页面可能有些优化点就不适用,还需要视实际情况和需求而定。另外微信小程序就是属于功能模块的类别,差不多是这个套路。

4.离线包以压缩包的方式下发,同时会经过加密和校验,运营商和第三方无法对其劫持篡改。

rails通过把静态资源变成erb模板文件,然后加入<%= asset_path
‘image.png’
%>,上线前预编译完成处理,不得不承认,fis的实现思路跟这个几乎完全一样,但我们当初确实不知道有rails的这套方案存在。

总结起来,大体优化思路就是:缓存/预加载/并行,缓存一切网络请求,尽量在用户打开之前就加载好所有内容,能并行做的事不串行做。这里有些优化手段需要做好一整套工具和流程支持,需要跟开发效率权衡,视实际需求优化。

1.简单的方案是如果本地离线包没有或不是最新,就同步阻塞等待下载最新离线包。这种用户打开的体验更差了,因为离线包体积相对较大。

让我们返璞归真,从原始的前端开发讲起。上图是一个“可爱”的index.html页面和它的样式文件a.css,用文本编辑器写代码,无需编译,本地预览,确认OK,丢到服务器,等待用户访问。前端就是这么简单,好好玩啊,门槛好低啊,分分钟学会有木有!

早期 web 页面里,JS 只是负责交互,所有内容都是直接在 HTML 里,到现代 H5
页面,很多内容已经依赖 JS 逻辑去决定渲染什么,例如等待 JS 请求 JSON
数据,再拼接成 HTML 生成 DOM
渲染到页面上,于是页面的渲染展现就要等待这一整个过程,这里有一个耗时,减少这里的耗时也是白屏优化的范围之内。

1.可以预先下载整个离线包,只需要按业务模块配置,不需要按文件配置,离线包包含业务模块相关的所有页面,可以一次性预加载。

等等,这还没完呢!对于大公司来说,那些变态的访问量和性能指标,将会让前端一点也不“好玩”。

HTML 缓存

HTML 和 JS/CSS/image 资源都属于静态文件,HTTP
本身提供了缓存协议,浏览器实现了这些协议,可以做到静态文件的缓存。

很好,相信有人想到了办法:通过更新页面中引用的资源路径,让浏览器主动放弃缓存,加载新资源。好像这样:

4.拦截网络请求,对于离线包已经有的文件,直接读取

第三种 Fallback
的方式还带来兜底的好处,在一些意外情况离线包出错的时候可以直接访问线上版本,功能不受影响,此外像公共资源包更新不及时导致版本没有对应上时也可以直接访问线上版本,是个不错的兜底方案。

这回再有文件修改,就只更新那个文件对应的url了,想到这里貌似很完美了。你觉得这就够了么?大公司告诉你:图样图森破!

Fallback

其中对首屏启动速度影响最大的就是网络请求,所以优化的重点就是缓存,这里着重说一下前端对请求的缓存策略。我们再细分一下,分成
HTML 的缓存,JS/CSS/image 资源的缓存,以及 json 数据的缓存。

这次发布,同时改了页面结构和样式,也更新了静态资源对应的url地址,现在要发布代码上线,亲爱的前端研发同学,你来告诉我,咱们是先上线页面,还是先上线静态资源?

如果用户访问某个离线包模块时,这个离线包还没有下载,或配置表检测到已有新版本但本地是旧版本的情况如何处理?几种方案:

更多优化

强制浏览器使用本地缓存(cache-control/expires),不要和服务器通信。好了,请求方面的优化已经达到变态级别,那问题来了:你都不让浏览器发资源请求了,这缓存咋更新?

2.缓存不可控:缓存的存取由系统 webview
控制,无法控制它的缓存逻辑,带来的问题包括:

另外上述讨论的是针对功能模块类的 H5 页面秒开的优化方案,客户端 APP
上除了功能模块,其他一些像营销活动/外部接入的 H5
页面可能有些优化点就不适用,还需要视实际情况和需求而定。另外微信小程序就是属于功能模块的类别,差不多是这个套路。

静态资源CDN部署                          —— 优化网络请求

这些问题在客户端上都是可以被解决的,只不过有点麻烦,简单描述下:

既然很多问题都是文件分散管理困难引起,而我们这里的使用场景是使用 H5
开发功能模块,那很容易想到把一个个功能模块的所有相关页面和资源打包下发,这个压缩包可以称为功能模块的离线包。使用离线包的方案,可以相对较简单地解决上述几个问题:

过程

5.离线包数据返回,否则走 HTTP 协议缓存逻辑。
离线包更新时,根据版本号后台下发两个版本间的 diff
数据,客户端合并,增量更新。

这样看起来已经比较完美了,HTML
文件在用客户端的策略缓存,其余资源和数据沿用上述前端的缓存方式,这样一个
H5 页面第二次访问从 HTML 到 JS/CSS/Image
资源,再到数据,都可以直接从本地读取,无需等待网络请求,同时又能保持尽可能的实时更新,解决了缓存问题,大大提升
H5 页面首屏启动速度。

有的页面没有js请求数据,但其他功能模块还是有的,请求当前用户信息,js向后端请求相关数据再渲染。等到下载渲染图片才是最后页面完整展示。

上述几种方案策略也可以混着使用,看业务需求。

预加载 webview

一些简单的页面可能没有 JS 请求数据
这一步,但大部分功能模块应该是有的,根据当前用户信息,JS
向后台请求相关数据再渲染,是常规开发方式。

4.在客户端使用 httpdns + https 防劫持。

具体实现上,首先可以在配置表注明某个离线包需要预加载的 URL,客户端在
webview
初始化同时发起请求,请求由一个管理器管理,请求完成时缓存结果,然后
webview 在初始化完毕后开始请求刚才预加载的
URL,客户端拦截到请求,转接到刚才提到的请求管理器,若预加载已完成就直接返回内容,若未完成则等待。

1.在客户端拦截请求,首次请求 HTML
文件后缓存数据,第二次不发请求,直接使用缓存数据。

1.首次预加载:在一个进程内首次初始化 webview
与第二次初始化不同,首次会比第二次慢很多。原因预计是 webview
首次初始化后,即使 webview 已经释放,但一些多 webview
共用的全局服务或资源对象仍没有释放,第二次初始化时不需要再生成这些对象从而变快。我们可以在
APP 启动时预先初始化一个 webview 然后释放,这样等用户真正走到 H5
模块去加载 webview时就变快了。

随着移动设备性能不断增强,web页面的性能体验逐渐变得可以接受,又因为 web
开发模式的诸多好处(跨平台,动态更新,减体积,无限扩展),APP
客户端里出现越来越多内嵌 web
页面(为了配上当前流行的说法,以下把所有网页都称为 H5 页面,虽然可能跟
H5 没关系),很多 APP 把一些功能模块改成用 H5 实现。

这些缓存策略可以实现 JS/CSS
等资源文件以及用户数据的缓存的全缓存,可以做到每次都直接使用本地缓存数据,不用等待网络请求。但
HTML 文件的缓存做不到,对于 HTML 文件,如果把 Expires / max-age
时间设长了,长时间只使用本地缓存,那更新就不及时,如果设短了,每次打开页面都要发网络请求询问是否有更新,再确定是否使用本地资源,一般前端在这里的策略是每次都请求,这在弱网情况下用户感受到的白屏时间仍然会很长。所以
HTML 文件的“缓存”和跟“更新”间存在矛盾。

先接着缓存说,在客户端有更自由的缓存策略,客户端可以拦截 H5
页面的所有请求,由自己管理缓存,针对上述 HTML
文件的“缓存”和“更新”之间的矛盾,我们可以用这样的策略解决:

第三种 Fallback
的方式还带来兜底的好处,在一些意外情况离线包出错的时候可以直接访问线上版本,功能不受影响,此外像公共资源包更新不及时导致版本没有对应上时也可以直接访问线上版本,是个不错的兜底方案。

本文出处链接:
https://mp.weixin.qq.com/s/ye1CeIjlfs9VSUab3gQI5g
作者:蚂蚁金服高级无线开发专家 bang

1.后端使用构建工具把同一个业务模块相关的页面和资源打包成一个文件,同时对文件加密/签名。

阿里妹导读:
越来越多的APP内业务使用H5的方式实现,怎样让H5页面启动更快是很多人在探索的技术点,本文梳理了启动过程中的各个点,分别从前端和客户端角度去探讨有哪些优化方案,供大家参考。

前端优化

上述打开一个页面的过程有很多优化点,包括前端和客户端,常规的前端和后端的性能优化在
PC 时代已经有最佳实践,主要的是:

网站地图xml地图