协慌网

登录 贡献 社区

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

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

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

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

更新:

在这里讨论了一段时间后,我发现John Millikinda5id的建议很有用。事实证明,有一个术语: 自动版本控制

我在下面发布了一个新的答案,它是我原来的解决方案和 John 的建议的组合。

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

由于不清楚伪造的查询字符串会发生什么,我不接受这个答案。

答案

更新:重写以纳入John Millikinda5id 的建议。此解决方案是用 PHP 编写的,但应该很容易适应其他语言。

更新 2:结合Nick Johnson 的评论,原始.htaccess正则表达式可能导致json-1.3.js等文件出现问题。解决方案是仅在末尾恰好有 10 位数时才重写。 (因为 10 位数字涵盖了从 9/9/2001 到 11/20/2286 的所有时间戳。)

首先,我们在. 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,因此它不会使用缓存副本。

这也适用于图像,favicon 和 JavaScript。基本上任何不动态生成的东西。

简单的客户端技术

一般来说,缓存很好.. 所以有几种技术,取决于你在开发网站时是否自己解决问题,或者你是否试图在生产环境中控制缓存。

您网站的一般访问者将无法获得您在开发网站时所拥有的相同体验。由于普通访问者访问网站的频率较低(可能每月只有几次,除非您是 Google 或 hi5 网络),因此他们不太可能将您的文件放在缓存中,这可能就足够了。如果要在浏览器中强制使用新版本,可以随时向请求添加查询字符串,并在进行重大更改时提高版本号:

<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>

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

值得注意的是,浏览器并不一定吝啬将文件保存在缓存中。浏览器有针对此类事情的策略,它们通常按照 HTTP 规范中规定的规则进行播放。当浏览器向服务器发出请求时,响应的一部分是 EXPIRES 标头.. 一个告诉浏览器应该在缓存中保留多长时间的日期。下次浏览器遇到对同一文件的请求时,它会看到它在缓存中有一个副本,并查看 EXPIRES 日期以决定是否应该使用它。

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

如果您对有关 HTTP 的更多信息或如何提出这些请求感兴趣,那么一本好书就是 Steve Souders 的 “高性能网站”。这是对这个主题的一个非常好的介绍。

谷歌的 apache mod_pagespeed插件会为你做自动版本控制。这真的很光滑。

它解析 HTML 从 Web 服务器出来(使用 PHP,rails,python,静态 HTML - 任何东西),并重写 CSS,JS,图像文件的链接,因此它们包含一个 id 代码。它在修改后的 URL 上提供文件,并对它们进行非常长的缓存控制。文件更改后,它会自动更改 URL,以便浏览器重新获取它们。它基本上只是工作,没有任何代码更改。它甚至会在出路时缩小你的代码。