我注意到一些浏览器(特别是Firefox和Opera)非常热衷于使用.css和.js文件的缓存副本,甚至在浏览器会话之间。当您更新其中一个文件时,这会导致一个问题,但用户的浏览器会继续使用缓存的副本。
当文件发生更改时,强迫用户浏览器重新加载文件的最优雅的方法是什么?
理想情况下,该解决方案不会强制浏览器在每次访问页面时重新加载文件。
我发现John Millikin和da5id的建议很有用。这有一个专门的术语:自动版本控制。
我在下面发布了一个新的答案,这是我最初的解决方案和约翰的建议的结合。
SCdF建议的另一个想法是将伪查询字符串附加到文件中。(一些自动使用时间戳作为伪查询字符串的Python代码是由pi..提交的)
然而,关于浏览器是否缓存带有查询字符串的文件还存在一些讨论。(请记住,我们希望浏览器缓存该文件并在以后的访问中使用它。我们只希望它在文件更改时再次获取该文件。)
下面是我基于Bash脚本的缓存破坏解决方案:
我假设在index.html文件中引用了CSS和JavaScript文件
在index.html中为.js和.css添加时间戳作为参数,如下所示(仅限一次)
用上面的时间戳创建一个timestamp.txt文件。
在对.css或.js文件进行任何更新后,只需运行下面的.sh脚本
带时间戳的.js和.css index.html示例条目:
<link rel="stylesheet" href="bla_bla.css?v=my_timestamp">
<script src="scripts/bla_bla.js?v=my_timestamp"></script>
文件timestamp.txt应该只包含相同的时间戳'my_timestamp'(稍后将被脚本搜索并替换)
最后这里是脚本(让我们称之为cache_buster.sh:D)
old_timestamp=$(cat timestamp.txt)
current_timestamp=$(date +%s)
sed -i -e "s/$old_timestamp/$current_timestamp/g" index.html
echo "$current_timestamp" >timestamp.txt
(Visual Studio Code用户)你可以把这个脚本放在一个钩子中,这样每次文件保存在你的工作空间时它都会被调用。
这个解决方案是用PHP编写的,但是应该很容易适应其他语言。
原始的.htaccess正则表达式可能会导致json-1.3.js等文件出现问题。解决方案是只有在结尾恰好有10位数字时才重写。(因为10位数字涵盖了从2001年9月9日到2286年11月20日的所有时间戳。)
首先,我们在.htaccess中使用下面的重写规则:
RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]
现在,我们编写以下PHP函数:
/**
* Given a file, i.e. /css/base.css, replaces it with a string containing the
* file's mtime, i.e. /css/base.1221534296.css.
*
* @param $file The file to be loaded. Must be an absolute path (i.e.
* starting with slash).
*/
function auto_version($file)
{
if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
return $file;
$mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}
现在,无论你在哪里包含你的CSS,从下面更改它:
<link rel="stylesheet" href="/css/base.css" type="text/css" />
:
<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />
这样,您就不必再次修改链接标签,用户将始终看到最新的CSS。浏览器将能够缓存CSS文件,但当您对CSS进行任何更改时,浏览器将看到这是一个新的URL,因此它不会使用缓存的副本。
这也适用于图像、favicons和JavaScript。基本上任何不是动态生成的东西。
我建议您使用实际CSS文件的MD5散列,而不是手动更改版本。
URL应该是这样的
http://mysite.com/css/[md5_hash_here]/style.css
您仍然可以使用重写规则来去除散列,但优点是现在您可以将缓存策略设置为“永远缓存”,因为如果URL相同,这意味着文件没有改变。
然后,您可以编写一个简单的shell脚本来计算文件的散列并更新标记(您可能希望将其移动到一个单独的文件中进行包含)。
只要在CSS每次更改时运行该脚本就可以了。浏览器只会在文件被修改时重新加载。如果你做了一个编辑,然后撤销它,为了让你的访问者不重新下载,你不需要确定需要返回到哪个版本。
对于一个2008年左右的网站来说,这30个左右的答案是很好的建议。然而,当涉及到现代的单页应用程序(SPA)时,可能是时候重新考虑一些基本假设了……特别是web服务器只提供文件的单个最新版本是理想的想法。
假设您是一个用户,浏览器中加载了SPA的M版本:
CD管道将应用程序的新版本N部署到服务器上
您在SPA中导航,它向服务器发送一个XMLHttpRequest (XHR)以获取/some.template
(您的浏览器没有刷新页面,所以您仍然在运行版本M)
服务器返回/some的内容。template -你想让它返回模板的版本M还是N ?
如果格式为/some。模板在版本M和N之间发生了变化(或者文件被重命名了等等),你可能不希望将版本N的模板发送到运行旧版本M解析器的浏览器。†
Web应用程序在满足两个条件时遇到这个问题:
在初始页面加载后的一段时间内异步请求资源
应用程序逻辑假设有关资源内容的事情(在将来的版本中可能会改变)
一旦你的应用程序需要并行提供多个版本,解决缓存和“重新加载”变得微不足道:
将所有site文件安装到versioned目录:/v<release_tag_1>/…files…,/v<release_tag_2>/…files…
设置HTTP头,让浏览器永远缓存文件
(或者更好的是,把所有东西都放在CDN中)
更新所有<script>和<link>标签等,以指向某个版本目录中的该文件
最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个URL调用URL构建器。或者您可以聪明地使用<base>标记并在一个地方更改当前版本。
†解决这个问题的一种方法是在新版本发布时强制浏览器重新加载所有内容。但是为了让任何正在进行的操作完成,至少并行支持两个版本:v-current和v-previous可能仍然是最简单的。