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

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

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


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

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

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

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


当前回答

我所知道的最好和最快的方法之一是更改存放CSS或JavaScript文件的文件夹的名称。

或者对于开发人员:更改CSS和JavaScript文件的名称,例如版本。

<link rel="stylesheet" href="cssfolder/somecssfile-ver-1.css"/>

对JavaScript文件执行同样的操作。

其他回答

你可以把?foo=1234放在CSS / JavaScript导入的末尾,把1234改成你喜欢的任何值。看一下Stack Overflow HTML源代码的例子。

这里的意思是?无论如何,参数都会在请求中被丢弃/忽略,您可以在推出新版本时更改这个数字。


注意:关于这究竟如何影响缓存存在一些争论。我认为它的一般要点是GET请求,无论是否带参数都应该是可缓存的,因此上面的解决方案应该是有效的。

然而,这取决于web服务器和用户使用的浏览器,因为它可以直接要求一个新的版本。

感谢Kip的完美解决方案!

我对它进行了扩展,将其用作Zend_view_Helper。因为我的客户端在虚拟主机上运行他的页面,我也为此扩展了它。

/**
 * Extend filepath with timestamp to force browser to
 * automatically refresh them if they are updated
 *
 * This is based on Kip's version, but now
 * also works on virtual hosts
 * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *
 * Usage:
 * - extend your .htaccess file with
 * # Route for My_View_Helper_AutoRefreshRewriter
 * # which extends files with there timestamp so if these
 * # are updated a automatic refresh should occur
 * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
 * - then use it in your view script like
 * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
 *
 */
class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

    public function autoRefreshRewriter($filePath) {

        if (strpos($filePath, '/') !== 0) {

            // Path has no leading '/'
            return $filePath;
        } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

            // File exists under normal path
            // so build path based on this
            $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
            return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
        } else {

            // Fetch directory of index.php file (file from all others are included)
            // and get only the directory
            $indexFilePath = dirname(current(get_included_files()));

            // Check if file exist relativ to index file
            if (file_exists($indexFilePath . $filePath)) {

                // Get timestamp based on this relativ path
                $mtime = filemtime($indexFilePath . $filePath);

                // Write generated timestamp to path
                // but use old path not the relativ one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {
                return $filePath;
            }
        }
    }
}

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

更改文件名也可以。但这通常不是最简单的解决方案。

正如你所注意到的,HTTP缓存控制标头为“no-cache”并不总是有效。HTTP 1.1规范为用户代理提供了决定是否请求新副本的回旋余地。(如果只看指令的名称,这是不直观的。去阅读实际的HTTP 1.1缓存规范…这在上下文中更有意义。)

简而言之,如果你想要严格的缓存控制使用

Cache-Control: no-cache, no-store, must-revalidate

在响应头中。

"Another idea which was suggested by SCdF would be to append a bogus query string to the file. (Some Python code to automatically use the timestamp as a bogus query string was submitted by pi.) However, there is some discussion as to whether or not the browser would cache a file with a query string. (Remember, we want the browser to cache the file and use it on future visits. We only want it to fetch the file again when it has changed.) Since it is not clear what happens with a bogus query string, I am not accepting that answer."

<link rel="stylesheet" href="file.css?<?=hash_hmac('sha1', session_id(), md5_file("file.css")); ?>" />

哈希文件意味着当文件发生变化时,查询字符串也会发生变化。如果没有,它将保持不变。每个会话也强制重新加载。

此外,您还可以使用重写使浏览器认为它是一个新的URI。