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

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

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


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

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

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

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


当前回答

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

其他回答

这里的许多答案主张向URL添加时间戳。除非直接修改生产文件,否则文件的时间戳不太可能反映文件更改的时间。在大多数情况下,这将导致URL比文件本身更频繁地更改。这就是为什么你应该使用文件内容的快速散列,如levik和其他人建议的MD5。

请记住,该值应该在构建或运行时计算一次,而不是在每次请求文件时计算一次。

例如,下面是一个简单的bash脚本,它从标准输入读取文件名列表,并将包含散列的JSON文件写入标准输出:

#!/bin/bash
# Create a JSON map from filenames to MD5 hashes
# Run as hashes.sh < inputfile.list > outputfile.json

echo "{"
delim=""
while read l; do
    echo "$delim\"$l\": \"`md5 -q $l`\""
    delim=","
done
echo "}"

然后,该文件可以在服务器启动时加载并引用,而不是读取文件系统。

如果使用jQuery,有一个叫做缓存的选项,它会附加一个随机数。

我知道这不是一个完整的答案,但它可能会节省你一些时间。

我最近用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文件将自动触发缓存失效。它工作得很好,开销也不明显。

对于我的开发,我发现Chrome有一个很好的解决方案。

https://superuser.com/a/512833

随着开发人员工具的打开,只需长按刷新按钮,让放手一旦你悬停在“空缓存和硬加载”。

这是我最好的朋友,是一种超轻量级的方式来得到你想要的!

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

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

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

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