来自 前端知识 2019-09-17 03:26 的文章
当前位置: 金沙澳门官网网址 > 前端知识 > 正文

性能优化,关于Web静态资源缓存自动更新的思考

静态资源发布的痛点

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

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

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

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

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

测试

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

  1. 首次载入页面
    澳门金莎娱乐手机版 1
  2. 更改index.js, 刷新页面
    澳门金莎娱乐手机版 2

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

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

如何解决?

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

  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>

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

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

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

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

前言

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

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

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

任选一种支付方式

澳门金莎娱乐手机版 3 澳门金莎娱乐手机版 4

1 赞 4 收藏 评论

【编辑推荐】

后记

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

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

澳门金莎娱乐手机版 , 打赏作者

HTTP/1

关于作者:Natumsol

澳门金莎娱乐手机版 5

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

澳门金莎娱乐手机版 6

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

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

分析

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

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

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

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

本文由金沙澳门官网网址发布于前端知识,转载请注明出处:性能优化,关于Web静态资源缓存自动更新的思考

关键词: