测试

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

  1. 首次载入页面
    bet36365注册送奖金 1
  2. 更改index.js, 刷新页面
    bet36365注册送奖金 2

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

bet36365注册送奖金,—————————- 我是一条分割线 —————————-

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

任选一种支付方式

bet36365注册送奖金 3
bet36365注册送奖金 4

1 赞 4 收藏
评论

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

后记

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

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


打赏作者

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

如何解决?

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

  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>

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

关于作者:Natumsol

bet36365注册送奖金 5

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

bet36365注册送奖金 6

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

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

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

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

前言

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

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

静态资源发布的痛点

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

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

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

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

链接:https://www.zhihu.com/question/20790576/answer/32602154

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

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

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

bet36365注册送奖金 7

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

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

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

bet36365注册送奖金 8

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

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

bet36365注册送奖金 9

bet36365注册送奖金 10

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

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

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

bet36365注册送奖金 11

来源:知乎

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

唉~~~~,让我喘口气

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

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

bet36365注册送奖金 12

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

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

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

bet36365注册送奖金 13

@陈钢

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图