我注意到一些浏览器(特别是Firefox和Opera)非常热衷于使用.css和.js文件的缓存副本,甚至在浏览器会话之间。当您更新其中一个文件时,这会导致一个问题,但用户的浏览器会继续使用缓存的副本。

当文件发生更改时,强迫用户浏览器重新加载文件的最优雅的方法是什么?

理想情况下,该解决方案不会强制浏览器在每次访问页面时重新加载文件。


我发现John Millikin和da5id的建议很有用。这有一个专门的术语:自动版本控制。

我在下面发布了一个新的答案,这是我最初的解决方案和约翰的建议的结合。

SCdF建议的另一个想法是将伪查询字符串附加到文件中。(一些自动使用时间戳作为伪查询字符串的Python代码是由pi..提交的)

然而,关于浏览器是否缓存带有查询字符串的文件还存在一些讨论。(请记住,我们希望浏览器缓存该文件并在以后的访问中使用它。我们只希望它在文件更改时再次获取该文件。)


当前回答

这个解决方案是用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。基本上任何不是动态生成的东西。

其他回答

我将文件内容的MD5哈希值放在其URL中。这样我就可以设置一个很长的过期日期,而不必担心用户使用旧的JS或CSS。

我还在运行时(或在文件系统更改时)对每个文件进行一次计算,因此在设计时或构建过程中没有什么有趣的事情要做。

如果你使用ASP。NET MVC,那么你可以在我的另一个答案中查看代码。

如果你添加session-id作为JavaScript/CSS文件的伪参数,你可以强制“会话范围缓存”:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

如果需要版本级缓存,可以添加一些代码来打印文件日期或类似内容。如果您正在使用Java,您可以使用自定义标记以一种优雅的方式生成链接。

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>

我不知道为什么你们要花这么大的力气来实现这个解决方案。

您所需要做的就是获取文件修改后的时间戳,并将其作为查询字符串附加到文件中。

在PHP中,我会这样做:

<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

filemtime()是一个PHP函数,返回修改后的文件时间戳。

简单的客户端技术

一般来说,缓存是好的…所以有一些技术,取决于你是在开发网站时为自己解决问题,还是在生产环境中控制缓存。

一般访问者不会有相同的体验,当你在开发网站时。由于一般访问者访问站点的频率较低(可能每个月只有几次,除非您是谷歌或hi5 Networks),那么他们不太可能将您的文件保存在缓存中,这可能就足够了。

如果你想强制一个新版本进入浏览器,你可以在请求中添加一个查询字符串,并在你进行重大更改时增加版本号:

<script src="/myJavascript.js?version=4"></script>

这将确保每个人都获得新文件。它可以工作,因为浏览器会查看文件的URL,以确定缓存中是否有副本。如果您的服务器没有设置为对查询字符串执行任何操作,那么它将被忽略,但是对于浏览器来说,该名称将看起来像一个新文件。

另一方面,如果您正在开发一个网站,您不希望每次保存对开发版本的更改时都更改版本号。那太乏味了。

所以当你在开发你的网站时,一个很好的技巧是自动生成一个查询字符串参数:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

在请求中添加一个查询字符串是一个资源版本化的好方法,但对于一个简单的网站来说,这可能是不必要的。记住,缓存是一件好事。

It's also worth noting that the browser isn't necessarily stingy about keeping files in cache. Browsers have policies for this sort of thing, and they are usually playing by the rules laid down in the HTTP specification. When a browser makes a request to a server, part of the response is an Expires header... a date which tells the browser how long it should be kept in cache. The next time the browser comes across a request for the same file, it sees that it has a copy in cache and looks to the Expires date to decide whether it should be used.

信不信由你,实际上是你的服务器使浏览器缓存如此持久。您可以调整服务器设置并更改Expires报头,但我上面写的小技巧可能是一种更简单的方法。由于缓存很好,所以您通常希望将日期设置为遥远的将来(“far -future Expires Header”),并使用上面描述的技术强制更改。

如果你想了解更多关于HTTP的信息或者这些请求是如何发出的,一本很好的书是Steve Souders的《高性能网站》。这是对这门学科很好的介绍。

下面是我基于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用户)你可以把这个脚本放在一个钩子中,这样每次文件保存在你的工作空间时它都会被调用。