对于FE工程师而言,对静态资源的缓存和更新一直是一个比较棘手的问题,各大公司也推出了各自的解决方案,如百度的FIS工具集。如果没有解决好这个问题,不仅会给用户造成糟糕的用户体验,而且还会给开发和调试带了很多不必要的麻烦。关于如何自动实现缓存更新,以下是自己的一点心得和体会。
静态资源发布的痛点
我们知道,静态资源缓存对于前端性能的优化是十分重要的,在正式发布项目的时候,对于所有静态资源比如各种JS工具库、CSS文件、背景图片等等我们会设置一个比较大的缓存过期时间(max-age),这些都会通过web服务器设置。
Exp:nginx设置expires设定静态资源缓存时间
location ~ \.(gif|jpg|jpeg|png|bmp|ico)$ { #图片缓存30天 root /var/www/img/; expires 30d; } location ~ .*\.(js|css)?$ { #js,css缓存12小时 expires 12h; }
当用户再次访问这个页面的时候就可以直接利用浏览器本地缓存而不是重新从服务器获取,这样不仅可以减轻服务端的压力,还可以节约网络传输的带宽流量,同时用户体验也更好(用户打开页面更快了)。这样看起来很完美,但是现实是残酷的。
现实中遇到的问题点:
- 当一个静态文件有bug时,一天内被多次修复更新,并要求保持其文件名不变,此时visitors的浏览器的静态文件因为没有超时(Last-Modified),还是老的静态文件,该怎么办?
- 假设存在这样一个浏览器,强制缓存静态资源还不给你清除缓存的机会(微信就是这样浏览器),即使你的服务端已更新,文件的Etag值已变化,但是微信就是不给你更新文件,该怎么办?
解决方案
方案一:静态文件名+版本号码的形式
Exp:http://www.baidu.com/static/demo/build.js?v=fd272aaef5
版本v可以是build.js文件的服务器修改时间或者是文件的md5值,总之只要是通过文件确定的唯一值!
模板文件
#!/bin/sh # 静态文件加戳脚本 # 静态文件包括:js、css、jpg、png、gif、swf function add_stamp(){ root_dir=$1 has_md5=$(whereis md5sum) file_list=$(grep -ro 'static/.*\.\(js\|css\|jpg\|gif\|png\|swf\)\(?[^\s]*"\)\?' $root_dir/views | sed 's/"$//g' | cut -d ':' -f 2) for f in $file_list;do rf=$(echo $f | cut -d '?' -f 1 ) if [[ x"$has_md5" != x ]];then stamp=$(md5sum $root_dir/$rf | awk '{print $1}') stamp=$(echo ${stamp:22}) echo $stamp else stamp=$(date "+%s") fi tpl=$(grep -rl "$rf" $root_dir/views) r="$rf?v=$stamp" from=$(echo $f | sed -e 's/\//\\\//g' -e 's/\?/\\?/g') to=$(echo $r | sed -e 's/\//\\\//g' -e 's/\?/\\?/g') sed -i "s/$from/$to/g" $tpl echo "在 `echo "$tpl" | awk -F"$root_dir/" '{print $2}'` 中扫描到静态文件引用,已加戳:$r" done } #修改目录下文件的版本号 add_stamp /mnt/hgfs/htdocs/yii2demo/1111 #结果 在 views/build.html 中扫描到静态文件引用,已加戳:static/demo/build.js?v=f76670ceba
此方案破解问题一,但不能破解问题二
方案二:重命名为原静态文件名 + 文件MD5值 + 文件后缀名的形式
Exp:http://www.baidu.com/static/demo/build-fd272aaef5.js
- 在每次发布项目之前,利用Gulp对所有的静态资源进行预处理,重命名为原静态文件名 + 文件MD5值 + 文件后缀名的形式。比如build.js重命名为build-fd272aaef5.js。
-
生成一份manifest,标明了预处理前后文件之间的对应关系.manifest文件的样子为:
{ "build.js": "build-fd272aaef5.js", }
- 在渲染视图模版的时候,根据manifest,将预处理前的静态资置换为预处理后的静态资源。
-
如果在浏览器端用到了模块加载器(这里以实现了AMD标准的requireJS为例),在每次发布的时候需要根据manifest对模块进行mapping,将配置文件以内联JS的形式写入到模版页面里面,类似于:
此方案技术比较复杂,但能从根本上解决所有静态文件缓存问题
总结:
关于前端性能优化,静态文件缓存一直是浓墨重彩的一笔。如果利用好缓存控制,不仅能提高用户体验,减少服务端流量压力,而且对于前端工程化的推进也是很有帮助的。
参考:
1.前端工程精粹(一):静态资源版本更新与缓存
2.关于Web静态资源缓存自动更新的思考与实践