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

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

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


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

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

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

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


当前回答

我发现在资源URL中使用基于时间戳或哈希的区分器的方法存在一个问题,该方法在服务器请求时被剥离。包含样式表链接的页面也会被缓存。因此,缓存的页面可能会请求较旧版本的样式表,但它将得到最新版本,这可能与请求页面不兼容,也可能与请求页面不兼容。

To fix this, you either have to guard the requesting page with a no-cache header or meta, to make sure it gets refreshed on every load. Or you have to maintain all versions of the style file that you ever deployed on the server, each as an individual file and with their differentiator intact so that the requesting page can get at the version of the style file it was designed for. In the latter case, you basically tie the versions of the HTML page and the style sheet together, which can be done statically and doesn't require any server logic.

其他回答

仅在纯JavaScript的本地开发中禁用script.js的缓存。

它会注入一个随机的script.js?Wizardry =1231234并阻塞常规script.js:

<script type="text/javascript">
  if(document.location.href.indexOf('localhost') !== -1) {
    const scr = document.createElement('script');
    document.setAttribute('type', 'text/javascript');
    document.setAttribute('src', 'scripts.js' + '?wizardry=' + Math.random());
    document.head.appendChild(scr);
    document.write('<script type="application/x-suppress">'); // prevent next script(from other SO answer)
  }
</script>

<script type="text/javascript" src="scripts.js">

这里的所有答案似乎都表明在命名方案中存在某种版本控制,但这也有其缺点。

浏览器应该通过读取web服务器的响应,特别是HTTP报头来清楚地知道什么该缓存,什么不该缓存——这个资源的有效期是多长?自上次检索该资源以来,该资源是否已更新?等。

如果事情配置“正确”,只是更新你的应用程序的文件应该(在某些时候)刷新浏览器的缓存。例如,你可以配置你的web服务器,告诉浏览器永远不缓存文件(这是一个坏主意)。

更深入的解释在Web缓存的工作原理中。

对于在开发和测试时遇到此问题的开发人员:

简单地删除缓存。

“保持缓存与文件一致”..太麻烦了。

一般来说,我不介意加载更多的文件——甚至在大多数项目中加载没有改变的文件——实际上是无关紧要的。在开发应用程序时,我们主要从磁盘加载,在localhost:port上加载,因此网络流量的增加不是一个棘手的问题。

大多数小项目只是玩玩而已——它们从来没有真正投入生产。所以对他们来说,你不需要更多的东西……

因此,如果你使用Chrome DevTools,你可以遵循如下图所示的禁用缓存方法:

如果你有Firefox缓存问题:

只在开发过程中这样做。你还需要一种机制来强制重新加载用于生产,因为如果你频繁地更新你的应用程序,你的用户将使用旧的缓存失效的模块,而你没有提供一个专门的缓存同步机制,就像上面的答案中描述的那样。

是的,这个信息已经在以前的答案中,但我仍然需要做谷歌搜索才能找到它。

在Laravel (PHP)中,我们可以以以下清晰而优雅的方式(使用文件修改时间戳)做到这一点:

<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>

CSS也是如此

<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">

示例HTML输出(filemtime返回时间作为Unix时间戳)

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">

我最近用Python解决了这个问题。下面是代码(它应该很容易被其他语言采用):

def import_tag(pattern, name, **kw):
    if name[0] == "/":
        name = name[1:]
    # Additional HTML attributes
    attrs = ' '.join(['%s="%s"' % item for item in kw.items()])
    try:
        # Get the files modification time
        mtime = os.stat(os.path.join('/documentroot', name)).st_mtime
        include = "%s?%d" % (name, mtime)
        # This is the same as sprintf(pattern, attrs, include) in other
        # languages
        return pattern % (attrs, include)
    except:
        # In case of error return the include without the added query
        # parameter.
        return pattern % (attrs, name)

def script(name, **kw):
    return import_tag('<script %s src="/%s"></script>', name, **kw)

def stylesheet(name, **kw):
    return import_tag('<link rel="stylesheet" type="text/css" %s href="/%s">', name, **kw)

这段代码基本上是将文件时间戳作为查询参数附加到URL。下面函数的调用

script("/main.css")

会导致

<link rel="stylesheet" type="text/css"  href="/main.css?1221842734">

当然,这样做的好处是您不必再次更改HTML内容,因为更改CSS文件将自动触发缓存失效。它工作得很好,开销也不明显。