前言
对于前端工程化而言,静态资源的缓存与更新一直是一个比较大的问题,各大公司也推出了各自的解决方案,如百度的FIS工具集。如果没有解决好这个问题,不仅会给用户造成糟糕的用户体验,而且还会给开发和调试带了很多不必要的麻烦。关于如何自动实现缓存更新,以下是自己的一点心得和体会。
静态资源发布的痛点
我们知道,缓存对于前端性能的优化是十分重要的,在正式发布系统的时候,对于那些不经常变动的静态资源比如各种JS工具库、CSS文件、背景图片等等我们会设置一个比较大的缓存过期时间(max-age),当用户再次访问这个页面的时候就可以直接利用缓存而不是重新从服务器获取,这样不仅可以减轻服务端的压力,还可以节约网络传输的流量,同时用户体验也更好(用户打开页面更快了)。这样看起来很完美,你好我好大家都好,but,理想是美好的,现实是残酷的,假设存在这样一个浏览器,强制缓存静态资源还不给你清除缓存的机会(微信,说的就是你!),该怎么办?即使你的服务端已更新,文件的Etag
值已变化,但是微信就是不给你更新文件…请允许我做一个悲伤的表情…
对于这个问题,我们很自然的想法是在每次发布新版本的时候给所有静态资源的请求后面加上一个版本参数或时间戳,类似于/js/indx.js?ver=1.0.1
,但是这样存在两个问题:
- 微信对于加参数的静态资源还是优先使用缓存版本(实际测试的情况是这样的)。
- 假如这样是可行的,那么对于没有变更的静态资源也会重新从服务器获取而不是读取缓存,没有充分利用缓存。
那么有没有一种方法可以自动分辨出哪个文件发生了变化并让客户端主动更新呢?答案是肯定的。我们知道一个文件的MD5
可以唯一标识一个文件。若文件发生了变化,文件的指纹值MD5
也随之变化。利用这个特性我们就可以标识出哪个静态资源发生了变化,并让客户端主动更新。
如何解决?
经过前文的介绍,我们知道了可以利用文件的指纹值来标识需要客户端主动更新的文件,但是如何实现呢?经过自己的思考和调研后,大致思路为:
- 在每次发布之前,利用
Gulp
对所有的静态资源进行预处理,重命名为原文件名
+文件MD5值
+文件后缀名
的形式。比如index.js
重命名为index-c6c9492ce6.js
。 - 生成一份
manifest
,标明了预处理前后文件之间的对应关系.manifest
文件的样子为:
1234567{"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"} - 在渲染视图模版的时候,根据
manifest
,将预处理前的静态资置换为预处理后的静态资源。 - 如果在浏览器端用到了模块加载器(这里以实现了AMD标准的
requireJS
为例),在每次发布的时候需要根据manifest
对模块进行mapping,将配置文件以内联JS的形式写入到模版页面里面,类似于:
12345678910111213<script>requirejs.config({"baseUrl": "/js","map": {"*": {"index": "index-c6c9492ce6","jquery": "lib/jQuery/jQuery-683c73084c","require": "require-c8e8015f8d","tools": "tools-5666ee48e9"}}});</script>
测试
为了验证可行性,自己做了个demo,代码托管在Github。经测试,可以完美的解决之前提出的问题。
我们发现,只有index.js
在更改后被主动更新了,其余的静态资源均是直接利用的缓存!。
后记
关于前端性能优化,缓存一直是浓墨重彩的一笔。如果利用好缓存控制,不仅能提高用户体验,减少服务端流量压力,而且对于前端工程化的推进也是很有帮助的。随着web系统的业务和功能的扩大,维护前端的任务将变得越来越繁重,按照历史规律,当一件事变得越来越繁重的时候,工程化是其唯一的出路。现在的前端还很年轻,工程化的概念提出来不久,但我相信,在各大互联网公司的前端们积极推动下,前端工程化必将成为业界标配。