如何强制浏览器重新加载缓存的 CSS 和 JavaScript 文件

IT技术 javascript css caching auto-versioning
2021-01-18 22:48:43

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

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

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


我发现John Millikinda5id 的建议很有用。事实证明,有一个术语:自动版本控制

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

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

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

6个回答

这个解决方案是用 PHP 编写的,但它应该很容易适应其他语言。

原始.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,因此它不会使用缓存的副本。

这也适用于图像、网站图标和 JavaScript。基本上任何不是动态生成的。

我看到了一个问题,它多次访问文件系统 - 确切地说 - 链接数 * 请求数/秒......这对您来说可能是也可能不是问题。
2021-03-09 22:48:43
@AlixAxel:不会,当参数改变时浏览器会重新获取它,但是一些公共代理不会缓存带有 url 参数的文件,所以最好的做法是在路径中包含版本。与 WPO 中的所有其他性能瓶颈相比,mod_rewrite 开销微乎其微
2021-03-14 22:48:43
@Kip:非常巧妙的解决方案。URL 重写显然不仅仅提供了漂亮的 url。
2021-03-19 22:48:43
我自己的静态内容服务器完全相同,除了我使用版本控制参数 (base.css?v=1221534296) 而不是文件名更改 (base.1221534296.css)。我怀疑你的方式可能会更有效率一点。很酷。
2021-03-30 22:48:43
第一次file_exists检查真的有必要吗?filemtime失败时将返回 false,那么为什么不将 filemtime 值分配给一个变量并在重命名文件之前检查它是否为 false?这将减少一个真正加起来的不必要的文件操作。
2021-04-05 22:48:43

简单的客户端技术

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

您网站的一般访问者将不会获得与您在开发网站时相同的体验。由于普通访问者访问该站点的频率较低(每个月可能只有几次,除非您是 Google 或 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>

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

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

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

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

这不是一个可行的解决方案。许多浏览器只会拒绝缓存任何带有查询字符串的内容。这就是为什么如果您有关于静态内容引用的查询字符串,Google、GTMetrix 和类似工具会引发标记的原因。虽然它肯定是一个不错的开发解决方案,但它绝对不是生产的解决方案。此外,浏览器控制缓存,而不是服务器。服务器只提示何时应该刷新;浏览器不必侦听服务器(通常也不必)。移动设备就是一个典型的例子。
2021-03-14 22:48:43
document.write 解决方案太好用了,现在我无法在 Chrome 中设置断点,因为 url 不断变化,因此不断刷新并丢失我的断点!
2021-03-17 22:48:43
在活跃的开发过程中,使用 Javascript 生成查询字符串的快速技巧非常有效。我用 PHP 做了同样的事情。
2021-04-02 22:48:43
当我使用以下内容时,这似乎不适用于我的 CSS: <link href='myCss.css?dev=14141'...>
2021-04-06 22:48:43
这是实现原始海报所需结果的最简单方法。如果您想在每次加载页面时强制重新加载 .css 或 .js 文件,则 mod_rewrite 方法效果很好。此方法仍然允许缓存,直到您实际更改文件并确实希望它强制重新加载。
2021-04-07 22:48:43

用于Apache 的Google 的mod_pagespeed插件将为您进行自动版本控制。真的很滑

它在离开网络服务器的路上解析 HTML(适用于 PHP、Ruby on Rails、Python、静态 HTML——任何东西)并重写 CSS、JavaScript、图像文件的链接,以便它们包含一个 id 代码。它在修改后的 URL 处提供文件,并对它们进行很长的缓存控制。当文件更改时,它会自动更改 URL,因此浏览器必须重新获取它们。它基本上可以正常工作,无需对您的代码进行任何更改。它甚至会在出路时缩小您的代码。

mod_pagespeed 在功能上等同于 html/css/js 的完全自动构建/编译步骤。我认为您很难找到任何认真的开发人员,他们认为构建系统本质上是错误的,或者完全自动化有什么问题。干净构建的类比是清除 mod_pagespeed 的缓存:code.google.com/p/modpagespeed/wiki/...
2021-03-15 22:48:43
@T4NK3R mod_pagespeed 不需要对您的源做任何事情来进行缓存管理,只是提到它可以帮助缩小之类的事情。至于它是否“错误”,那完全是主观的。这对你来说可能是错误的,但这并不意味着它天生就不好
2021-03-23 22:48:43
太好了,但仍处于测试阶段。可以用于企业服务吗?
2021-03-26 22:48:43
当这显然是浏览器问题时,这是错误的(自动摆弄源代码)。给我们(开发人员)一个真正的大脑擦拭刷新:<ctrl>+F5
2021-04-02 22:48:43
它也适用于 nginx,但您必须从源代码构建它:developers.google.com/speed/pagespeed/module/...
2021-04-03 22:48:43

我建议您使用实际 CSS 文件的 MD5 哈希值,而不是手动更改版本。

所以你的网址会是这样的

http://mysite.com/css/[md5_hash_here]/style.css

您仍然可以使用重写规则去除散列,但优点是现在您可以将缓存策略设置为“永久缓存”,因为如果 URL 相同,则意味着文件未更改。

然后,您可以编写一个简单的 shell 脚本来计算文件的哈希值并更新您的标签(您可能希望将其移动到一个单独的文件中以包含在内)。

每次 CSS 更改时只需运行该脚本即可。浏览器只会在您的文件被更改时重新加载它们。如果您进行了编辑然后撤消它,那么确定您需要返回哪个版本以便您的访问者不会重新下载是很容易的。

非常好的解决方案..但我认为计算每个页面访问的每个文件请求(css、js、图像、html..等)中文件的哈希值是消耗资源的。
2021-03-09 22:48:43
@DeepBlue - 回答说“每次 CSS 更改时运行该脚本”这不是每次访问页面时都会出现的情况。OTOH 答案遗漏了主要细节 - 更改后的哈希值如何成为 URL 的一部分?我不知道...
2021-03-19 22:48:43
不幸的是,我不知道如何实现它。请提供建议...更多详情...
2021-03-27 22:48:43
对于那些使用 js 或 css 与 gulp、grunt 或 webpack 捆绑的人来说,这是一个标准的解决方案,每个解决方案的实现都不同,但是将文件散列作为构建步骤很常见,建议用于现代捆绑应用程序
2021-03-27 22:48:43
在 shell、ruby 等中实现会很棒
2021-04-02 22:48:43

我不知道为什么你们/女孩们为了实施这个解决方案而付出这么多的痛苦。

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

在 PHP 中,我会这样做:

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

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

很好的解决方案。此外,我发现 filemtime 不适用于完全限定的域名 (FQDN),因此我将 FQDN 用于 href 部分,将 $_SERVER["DOCUMENT_ROOT"] 用于 filemtime 部分。例如:<link rel="stylesheet" href="http://theurl/mycss.css?v=<?php echo filemtime($_SERVER["DOCUMENT_ROOT"] .'/mycss.css') ?>"/>
2021-03-09 22:48:43
万分感谢。简单又好。这是在 Python 中: progpath = os.path.dirname(sys.argv[0]) def versionize(file): timestamp = os.path.getmtime('%s/../web/%s' % (progpath , file)) return '%s?v=%s' % (file, timestamp) print <link href="%s" rel="stylesheet" ' 'type="text/css" />' \ % versionize( 'css/main.css')
2021-03-14 22:48:43
非常优雅,尽管我已将其稍微修改为<link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>,以防万一该线程中有关使用 GET 变量缓存 URL 的一些参数(采用建议的格式)是正确的
2021-03-15 22:48:43
除了我的最后一条评论,我已经看到 wordpress 使用,?ver=所以谁知道呢!
2021-04-07 22:48:43
你可以只使用mycss.css?1234567890.
2021-04-08 22:48:43