如何强制浏览器重新加载缓存的 CSS / JS 文件?

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

问题是:强制用户浏览器在文件更改后重新加载文件的最优雅方法是什么?

理想情况下,该解决方案不会强制浏览器在每次访问页面时重新加载文件。我将发布自己的解决方案作为答案,但我很好奇是否有人有更好的解决方案,我将让您决定。

更新:

经过一段时间的讨论后,我发现John Millikinda5id的建议很有用。事实证明有一个术语: 自动版本化

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

SCdF建议的另一个想法是将伪查询字符串附加到文件中。 (一些由pi提交的将时间戳自动用作伪查询字符串的 Python 代码。)但是,关于浏览器是否将使用查询字符串缓存文件存在一些讨论。 (请记住,我们希望浏览器缓存文件并在以后的访问中使用它。我们只希望它在更改后再次获取文件。)

由于尚不清楚假查询字符串会发生什么,因此我不接受该答案。

答案

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]
/**
 *  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);
}
<link rel="stylesheet" href="/css/base.css" type="text/css" />
<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />
<script src="/myJavascript.js?version=4"></script>
<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

Google 的针对 Apache 的mod_pagespeed插件将为您完成自动版本控制。真的很滑。

它以离开网络服务器的方式解析 HTML(可用于 PHP,rails,python,静态 HTML 等等),并重写指向 CSS,JS,图像文件的链接,以便它们包含 id 代码。它将修改后的 URL 上的文件提供给文件,并具有很长的缓存控制。文件更改时,它会自动更改 URL,因此浏览器必须重新获取它们。它基本上可以正常工作,而无需更改您的代码。它甚至还会使您的代码最小化。

http://mysite.com/css/[md5_hash_here]/style.css
<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

您可以将?foo=1234放在 css / js 导入的末尾,将 1234 更改为您喜欢的任何值。请看 SO html 源代码示例。

有那个想法吗?无论如何,参数都会被丢弃 / 忽略,并且在您推出新版本时可以更改该数字。


注意:关于它如何影响缓存,存在一些争论。我认为一般的主旨是带有或不带参数的 GET 请求都应该是可缓存的,因此上述解决方案应该可行。

但是,由 Web 服务器决定是否要遵守该部分规范以及用户使用的浏览器,这是因为 Web 服务器可以直接要求任何版本。

我听说这叫做 “自动版本控制”。最常见的方法是将静态文件的 mtime 包含在 URL 中的某个位置,然后使用重写处理程序或 URL conf 将其删除:

也可以看看:

  • 在初始页面加载后的某个时间异步请求资源
  • 应用逻辑假定有关资源内容的事情(在将来的版本中可能会更改)

一旦您的应用需要并行提供多个版本, 解决缓存和 “重新加载” 就变得不那么重要了:

  1. 将所有站点文件安装到版本目录中: /v<release_tag_1>/…files…/v<release_tag_2>/…files…
  2. 设置 HTTP 标头,以使浏览器永远缓存文件
    • (或者更好的是,将所有内容都放入 CDN 中)
  3. 更新所有<script><link>标记等,以指向其中一个版本化目录中的文件

最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个 URL 调用 URL 构建器。或者,您可以巧妙地使用<base>标记,然后在一个位置更改当前版本。

†解决此问题的方法之一是在发布新版本时强制浏览器重新加载所有内容。但是,为了让任何正在进行的操作完成,可能仍然最容易并行支持至少两个版本:v-current 和 v-previous。

不要使用 foo.css?version = 1!浏览器不应该缓存带有 GET 变量的 URL。根据http://www.thinkvitamin.com/features/webapps/serving-javascript-fast 的说法,尽管 IE 和 Firefox 忽略了这一点,但 Opera 和 Safari 却没有!相反,请使用 foo.v1234.css,并使用重写规则删除版本号。

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]