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

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

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


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

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

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

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


当前回答

您可以使用SRI来破坏浏览器缓存。你只需要每次都用新的SRI哈希更新index.html文件。当浏览器加载HTML并发现HTML页面上的SRI散列与资源的缓存版本不匹配时,它将从服务器重新加载资源。它还带来了绕过跨起点读阻塞的良好副作用。

<script src="https://jessietessie.github.io/google-translate-token-generator/google_translate_token_generator.js" integrity="sha384-muTMBCWlaLhgTXLmflAEQVaaGwxYe1DYIf2fGdRkaAQeb4Usma/kqRWFWErr2BSi" crossorigin="anonymous"></script>

其他回答

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

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

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

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

在响应头中。

对现有答案的小改进……

使用随机数或会话id将导致它在每个请求时重新加载。理想情况下,我们可能只需要在任何JavaScript或CSS文件中完成一些代码更改时才需要更改。

当使用一个公共JSP文件作为许多其他JSP和JavaScript文件的模板时,请在一个公共JSP文件中添加以下内容

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var = "version" scope = "application" value = "1.0.0" />

现在在JavaScript文件包含的所有位置使用上述变量,如下所示。

<script src='<spring:url value="/js/myChangedFile.js?version=${version}"/>'></script>

优点:

这种方法将帮助您仅在一个位置更改版本号。

维护一个适当的版本号(通常是构建/发布号)将帮助您检查/验证您的代码更改是否被正确部署(从浏览器的开发人员控制台)。

另一个有用的建议:

如果使用Chrome浏览器,可以在打开“开发工具”时禁用缓存。 在Chrome浏览器中,点击F12→F1,滚动到设置→首选项→网络→*禁用缓存(当DevTools打开时)

JavaScript文件的另一种方法是使用jQuery $。与$. getScript结合使用。ajaxSetup选项缓存:false。

而不是:

<script src="scripts/app.js"></script>

你可以使用:

$.ajaxSetup({
  cache: false
});

$.getScript('scripts/app.js'); // GET scripts/app.js?_1391722802668

我已经通过使用解决了这个问题 ETag:

ETag或实体标签是HTTP的一部分,HTTP是万维网协议。它是HTTP为Web缓存验证提供的几种机制之一,这种机制允许客户机发出有条件的请求。这使得缓存更加高效并节省带宽,因为如果内容没有更改,Web服务器不需要发送完整的响应。ETags还可以用于乐观并发控制,1作为一种帮助防止资源的同步更新相互覆盖的方法。

I am running a Single-Page Application (written in Vue.JS). The output of the application is built by npm, and is stored as dist folder (the important file is: dist/static/js/app.my_rand.js) Nginx is responsible of serving the content in this dist folder, and it generates a new Etag value, which is some kind of a fingerprint, based on the modification time and the content of the dist folder. Thus when the resource changes, a new Etag value is generated. When the browser requests the resource, a comparison between the request headers and the stored Etag, can determine if the two representations of the resource are the same, and could be served from cache or a new response with a new Etag needs to be served.

对于一个2008年左右的网站来说,这30个左右的答案是很好的建议。然而,当涉及到现代的单页应用程序(SPA)时,可能是时候重新考虑一些基本假设了……特别是web服务器只提供文件的单个最新版本是理想的想法。

假设您是一个用户,浏览器中加载了SPA的M版本:

CD管道将应用程序的新版本N部署到服务器上 您在SPA中导航,它向服务器发送一个XMLHttpRequest (XHR)以获取/some.template

(您的浏览器没有刷新页面,所以您仍然在运行版本M)

服务器返回/some的内容。template -你想让它返回模板的版本M还是N ?

如果格式为/some。模板在版本M和N之间发生了变化(或者文件被重命名了等等),你可能不希望将版本N的模板发送到运行旧版本M解析器的浏览器。†

Web应用程序在满足两个条件时遇到这个问题:

在初始页面加载后的一段时间内异步请求资源 应用程序逻辑假设有关资源内容的事情(在将来的版本中可能会改变)

一旦你的应用程序需要并行提供多个版本,解决缓存和“重新加载”变得微不足道:

将所有site文件安装到versioned目录:/v<release_tag_1>/…files…,/v<release_tag_2>/…files… 设置HTTP头,让浏览器永远缓存文件

(或者更好的是,把所有东西都放在CDN中)

更新所有<script>和<link>标签等,以指向某个版本目录中的该文件

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

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