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

How to force the browser to reload cached CSS/JS files?

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

问题是:当文件发生变化时,最优雅的强制用户浏览器重新加载文件的方法是什么?

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

更新:

在这里讨论了一会儿之后,我发现约翰米利金和达5id的建议是有用的。事实证明,这有一个术语:自动版本控制。

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

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

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


更新:重写以包含来自JohnMillikin和DA5ID的建议。这个解决方案是用PHP编写的,但是应该很容易适应其他语言。

更新2:结合Nick Johnson的评论,原来的.htaccessregex可能导致json-1.3.js等文件出现问题。解决方案是只有在结尾正好有10个数字时才重写。(因为10位数字涵盖了从2001年9月9日到2286年11月20日的所有时间戳。)

首先,我们在.htaccess中使用以下重写规则:

1
2
RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

现在,我们编写以下PHP函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 *  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,都可以从此处更改它:

1
<link rel="stylesheet" href="/css/base.css" type="text/css" />

对此:

1
<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

这样,您就不再需要修改链接标签了,用户将始终看到最新的CSS。浏览器将能够缓存CSS文件,但是当您对CSS进行任何更改时,浏览器会将其视为新的URL,因此不会使用缓存的副本。

这也可以用于图像、传真和JavaScript。基本上任何不是动态生成的。


简单客户端技术

一般来说,缓存很好。因此,有两种技术,这取决于您是在开发网站时自己解决问题,还是在生产环境中控制缓存。

你网站的一般访问者不会有和你开发网站时相同的体验。由于一般的访问者访问该网站的频率较低(可能每个月只有几次,除非你是Google或Hi5网络),所以他们不太可能将你的文件保存在缓存中,这可能就足够了。如果要强制浏览器中使用新版本,可以始终向请求添加查询字符串,并在进行重大更改时增大版本号:

1
<script src="/myJavascript.js?version=4">

这将确保每个人都得到新文件。它工作是因为浏览器查看文件的URL以确定其缓存中是否有副本。如果您的服务器没有设置为对查询字符串执行任何操作,它将被忽略,但在浏览器中,该名称将看起来像一个新文件。

另一方面,如果您正在开发一个网站,则不希望每次保存对开发版本的更改时都更改版本号。那太无聊了。

因此,在开发站点时,一个好的技巧是自动生成一个查询字符串参数:

1
2
<!-- Development version: -->
document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');

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

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

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

如果您对HTTP上的更多信息或这些请求是如何发出的感兴趣,那么一本好书就是SteveSouders的"高性能网站"。这是对这个主题的很好的介绍。


谷歌针对Apache的mod pagespeed插件将为您进行自动版本控制。它真的很光滑。

它在离开Web服务器的过程中解析HTML(与PHP、Rails、Python、静态HTML——任何东西一起工作),并重写到CSS、JS、图像文件的链接,以便包含ID代码。它在经过修改的URL上为文件提供一个非常长的缓存控制。当文件更改时,它会自动更改URL,因此浏览器必须重新获取它们。它基本上就是工作的,不需要对代码做任何更改。它甚至会在退出时缩小您的代码。


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

所以你的网址应该是

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

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

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

只要在每次CSS更改时运行该脚本,就可以了。浏览器将仅在更改文件时重新加载文件。如果你做了一个编辑然后撤销它,你就不必费心去弄清楚你需要返回到哪个版本,这样你的访问者就不会重新下载了。


不知道你们为什么要花这么多的时间来实现这个解决方案。

如果获取文件的修改时间戳并将其作为查询字符串附加到文件中,则只需执行所有操作。

在PHP中,我可以这样做:

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

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


您可以将?foo=1234放在CSS/JS导入的末尾,将1234更改为您喜欢的任何内容。以so html源代码为例。

这里的想法是什么?参数在请求中被丢弃/忽略,您可以在推出新版本时更改该数字。

注意:对于这到底如何影响缓存有一些争论。我相信它的一般要点是GET请求,无论有没有参数都应该是可计算的,所以上述的解决方案应该是有效的。

然而,这取决于Web服务器决定是否要遵守规范的这一部分以及用户使用的浏览器,因为它可以直接进行并要求一个新的版本。


我听说这叫做"自动版本控制"。最常见的方法是将静态文件的mtime包含在URL中的某个位置,然后使用重写处理程序或url conf将其删除:

参见:

  • Django中的自动资产版本控制
  • 自动修改CSS和javascript文件的版本


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

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

  • CD管道将应用程序的新版本n部署到服务器上
  • 您可以在SPA中导航,SPA会向服务器发送一个XHR,以获取/some.template
    • (您的浏览器没有刷新页面,因此您仍在运行M版)
  • 服务器用/some.template的内容响应—您想让它返回模板的M或N版本吗?
  • 如果/some.template的格式在版本m和n(或文件被重命名或其他)之间发生了更改,您可能不希望模板的版本n发送到运行旧版本m分析器的浏览器。

    当满足以下两个条件时,Web应用程序会遇到此问题:

    • 在初始页面加载之后的某个时间异步请求资源
    • 应用程序逻辑假定有关资源内容的事情(在未来的版本中可能会改变)。

    一旦你的应用需要并行提供多个版本,解决缓存和"重新加载"就变得很简单:

  • 将所有站点文件安装到版本化目录中:/v/…files…/v/…files…
  • 设置HTTP头以允许浏览器永久缓存文件
    • (或者更好的是,将所有内容都放到cdn中)
  • 更新所有标记等,以指向其中一个版本化目录中的文件。
  • 最后一步听起来很棘手,因为可能需要为服务器端或客户端代码中的每个URL调用URL生成器。或者您可以巧妙地使用标记,在一个地方更改当前版本。

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


    不使用foo.css?版本=1!浏览器不应该使用get变量缓存URL。根据http://www.thinkvitamin.com/features/webapps/serving-javascript-fast,虽然IE和火狐忽略了这一点,但Opera和Safari却没有!相反,使用foo.v1234.css,并使用重写规则删除版本号。


    RewriteRule需要对JS或CSS文件进行少量更新,这些文件在末尾包含一个点标记版本控制。例如,JSON-1.3.JS。

    我在regex so.number中添加了一个点否定类[^]。被忽略。

    1
    RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]


    对于ASP.NET 4.5及更高版本,可以使用脚本绑定。

    The request http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 is for the bundle AllMyScripts and contains a query string pair v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. The query string v has a value token that is a unique identifier used for caching. As long as the bundle doesn't change, the ASP.NET application will request the AllMyScripts bundle using this token. If any file in the bundle changes, the ASP.NET optimization framework will generate a new token, guaranteeing that browser requests for the bundle will get the latest bundle.

    捆绑还有其他好处,包括在首次页面加载时通过缩小提高性能。


    有趣的帖子。在阅读了这里的所有答案后,再加上我从来没有遇到过"伪"查询字符串的任何问题(我不确定为什么每个人都如此不愿意使用这个问题),我猜想解决方案(它消除了对Apache重写规则的需要,就像在接受的答案中一样)是计算CSS文件内容的简短哈希(而不是文件datetime)作为伪查询字符串。

    这将导致以下结果:

    1
    <link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />

    当然,在编辑CSS文件的情况下,datetime解决方案也可以完成这项工作,但我认为这是关于CSS文件内容,而不是关于文件datetime,所以为什么要混淆这些内容呢?


    这里有一个纯JavaScript解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    (function(){

        // Match this timestamp with the release of your code
        var lastVersioning = Date.UTC(2014, 11, 20, 2, 15, 10);

        var lastCacheDateTime = localStorage.getItem('lastCacheDatetime');

        if(lastCacheDateTime){
            if(lastVersioning > lastCacheDateTime){
                var reload = true;
            }
        }

        localStorage.setItem('lastCacheDatetime', Date.now());

        if(reload){
            location.reload(true);
        }

    })();

    上面将查找用户上次访问您的站点的时间。如果上次访问是在您发布新代码之前,它使用location.reload(true)强制从服务器刷新页面。

    我通常把这个脚本作为中的第一个脚本,所以它在加载任何其他内容之前进行评估。如果需要重新加载,用户很难注意到。

    我正在使用本地存储将上次访问时间戳存储在浏览器上,但是如果您希望支持旧版本的IE,可以将cookie添加到混合物中。


    在laravel(php)中,我们可以用以下清晰、优雅的方式(使用文件修改时间戳)完成此操作:

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

    与CSS类似

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


    未找到动态创建脚本节点(或css)元素的客户端DOM方法:

    1
    2
    3
    4
        var node = document.createElement("script");
        node.type ="text/javascript";
        node.src = 'test.js?'+Math.floor(Math.random()*999999999);
        document.getElementsByTagName("head")[0].appendChild(node);

    感谢Kip的完美解决方案!

    我扩展了它,将其用作Zend_View_助手。因为我的客户机在虚拟主机上运行他的页面,所以我对它进行了扩展。

    希望它也能帮助别人。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    /**
     * Extend filepath with timestamp to force browser to
     * automatically refresh them if they are updated
     *
     * This is based on Kip's version, but now
     * also works on virtual hosts
     * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
     *
     * Usage:
     * - extend your .htaccess file with
     * # Route for My_View_Helper_AutoRefreshRewriter
     * # which extends files with there timestamp so if these
     * # are updated a automatic refresh should occur
     * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
     * - then use it in your view script like
     * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
     *
     */

    class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

        public function autoRefreshRewriter($filePath) {

            if (strpos($filePath, '/') !== 0) {

                // path has no leading '/'
                return $filePath;
            } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

                // file exists under normal path
                // so build path based on this
                $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
                return preg_replace('{\\.([^./]+)$}',".$mtime.\$1", $filePath);
            } else {

                // fetch directory of index.php file (file from all others are included)
                // and get only the directory
                $indexFilePath = dirname(current(get_included_files()));

                // check if file exist relativ to index file
                if (file_exists($indexFilePath . $filePath)) {

                    // get timestamp based on this relativ path
                    $mtime = filemtime($indexFilePath . $filePath);

                    // write generated timestamp to path
                    // but use old path not the relativ one
                    return preg_replace('{\\.([^./]+)$}',".$mtime.\$1", $filePath);
                } else {

                    return $filePath;
                }
            }
        }

    }

    干杯,谢谢。


    您可以简单地用CSS/JS URL添加一些随机数,比如

    1
    example.css?randomNo=Math.random()

    对于ASP.NET,我假设下一个具有高级选项的解决方案(调试/发布模式、版本):

    JS或CSS文件包括:

    1
    2
    <script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
    <link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

    global.jspostfix和global.csspostfix在global.asax中按以下方式计算:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    protected void Application_Start(object sender, EventArgs e)
    {
        ...
        string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
        bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
        int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
        JsPostfix ="";
    #if !DEBUG
        JsPostfix +=".min";
    #endif      
        JsPostfix +=".js?" + jsVersion +"_" + buildNumber;
        if (updateEveryAppStart)
        {
            Random rand = new Random();
            JsPosfix +="_" + rand.Next();
        }
        ...
    }

    如果将会话ID添加为JS/CSS文件的伪参数,则可以强制执行"会话范围缓存":

    1
    2
    <link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
    <script language="javascript" src="myCode.js?ABCDEF12345sessionID">

    如果您想要一个版本范围的缓存,您可以添加一些代码来打印文件日期或类似的文件。如果使用Java,可以使用自定义标记以优雅的方式生成链接。

    1
    2
    <link rel="stylesheet" src="myStyles.css?20080922_1020" />
    <script language="javascript" src="myCode.js?20080922_1120">

    假设您有一个文件可在:

    1
    /styles/screen.css

    您可以将带有版本信息的查询参数附加到URI上,例如:

    1
    /styles/screen.css?v=1234

    或者您可以预先发送版本信息,例如:

    1
    /v/1234/styles/screen.css

    imho第二种方法更适合于CSS文件,因为它们可以使用相对URL引用图像,这意味着如果您像这样指定background-image

    1
    2
    3
    body {
        background-image: url('images/happy.gif');
    }

    其URL将有效地为:

    1
    /v/1234/styles/images/happy.gif

    这意味着,如果更新所使用的版本号,服务器会将其视为新资源,而不使用缓存版本。如果您的版本号基于subversion/cvs/etc.revision,这意味着将注意到对css文件中引用的图像所做的更改。第一个方案不能保证,即相对于/styles/screen.css?v=1235的url images/happy.gif/styles/images/happy.gif,不包含任何版本信息。

    我已经使用JavaServlet实现了一个缓存解决方案,并简单地用委托给底层资源的Servlet处理EDCOX1 4请求(即EDCOX1(5))。在开发模式下,我设置缓存头,告诉客户机始终使用服务器检查资源的新鲜度(如果您委托给Tomcat的DefaultServlet.css.js等文件,这通常会导致304。而在部署模式下,我设置头,说"永远缓存"。


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

    https://developer.chrome.com/devtools/docs/tips and tricks硬加载

    在开发人员工具打开的情况下,只需长时间单击刷新按钮,并在鼠标悬停在"空缓存和硬重新加载"上时松开。

    这是我最好的朋友,而且是一个超轻的方式得到你想要的!


    我最近用python解决了这个问题。这里的代码(应该很容易采用其他语言):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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 type="text/javascript"""" +\
           """ %s src="/%s">""", name, **kw)

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

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

    1
    script("/main.css")

    将导致

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

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


    Google Chrome有硬加载、空缓存和硬加载选项。您可以单击并按住"重新加载"按钮(在检查模式下)来选择一个。


    我将文件内容的MD5散列放到其URL中。这样我就可以设置一个非常长的到期日期,而不必担心用户使用旧的JS或CSS。

    我也会在运行时(或在文件系统更改时)为每个文件计算一次,这样在设计时或构建过程中就没有什么有趣的事情了。

    如果您使用的是ASP.NET MVC,那么您可以在这里查看我的另一个答案中的代码。


    很抱歉把死线带回来了。

    托马是对的。

    使用"querystring"方法不会被缓存,如下面Steve Souders引用的那样:

    ...that Squid, a popular proxy, doesn’t cache resources with a
    querystring.

    @使用style.timestamp.css的toma建议很好,但是md5会更好,因为只有当内容真正改变时,md5也会改变。


    我建议实施以下过程:

    • 每次部署时都要对CSS/JS文件进行版本控制,比如:screen.1233.css(如果使用版本控制系统,这个数字可以是您的SVN版本号)。

    • 缩小它们以优化加载时间


    这里的所有答案似乎都暗示了命名方案中的某种版本控制,这有其缺点。

    浏览器应该通过读取WebServers响应,特别是HTTP头,很好地了解缓存什么和不缓存什么——这个资源的有效期有多长?自从我上次检索到这个资源后,它是否被更新了?等等。

    如果事情配置"正确",那么更新应用程序的文件(在某个时刻)就应该刷新浏览器缓存。例如,您可以将Web服务器配置为告诉浏览器从不缓存文件(这是一个坏主意)。

    这里有一个更深入的解释https://www.mnot.net/cache_docs/工作


    只需使用服务器端代码添加文件的日期…这样,它将被缓存,并且只有在文件更改时才重新加载。

    ASP.NET

    1
    2
    3
    <link rel="stylesheet" href="~/css/custom.css?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/css/custom.css")).ToString(),"[^0-9]",""))" />

    <script type="text/javascript" src="~/js/custom.js?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/js/custom.js")).ToString(),"[^0-9]",""))">

    这可以简化为:

    1
    <script src="<%= Page.ResolveClientUrlUnique("~/js/custom.js") %>" type="text/javascript">

    通过向项目添加扩展方法来扩展页面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static class Extension_Methods
    {
        public static string ResolveClientUrlUnique(this System.Web.UI.Page oPg, string sRelPath)
        {
            string sFilePath = oPg.Server.MapPath(sRelPath);
            string sLastDate = System.IO.File.GetLastWriteTime(sFilePath).ToString();
            string sDateHashed = System.Text.RegularExpressions.Regex.Replace(sLastDate,"[^0-9]","");

            return oPg.ResolveClientUrl(sRelPath) +"?d=" + sDateHashed;
        }
    }

    只需添加此代码,在其中执行硬重载(强制浏览器重新加载缓存的css/js文件)在.load内执行此操作,这样它就不会像循环一样刷新

    1
    2
    3
     $( window ).load(function() {
       location.reload(true);
    });


    如果使用的是git+php,则每次git repo发生更改时,都可以使用以下代码从缓存重新加载脚本:

    1
    2
    exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
    echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'>'.PHP_EOL;

    如果您使用的是现代浏览器,则可以使用清单文件通知浏览器哪些文件需要更新。这不需要标题,URL中没有版本等…

    有关详细信息,请参阅:请参见:https://developer.mozilla.org/nl/docs/web/html/applicatie_-cache_-gebruiken简介


    我将这个答案添加为Silverstripe http://www.silverstripe.org特定的答案,我一直在寻找这个答案,但从未找到这个答案,但从阅读中得到了答案:http://api.silverstripe.org/3.0/source-class-ss_datetime.html 98-110

    希望这将有助于使用silverstripe模板并尝试在每次访问/刷新页面时强制重新加载缓存图像。在我的例子中,它是一个只播放一次的GIF动画,因此在缓存后不会重放。在我的模板中,我简单地添加了:

    1
    ?$Now.Format(dmYHis)

    到文件路径的末尾,以创建唯一的时间戳并强制浏览器将其视为新文件。


    对于Java servlet环境,您可以查看JAWR库。"功能"页解释了它如何处理缓存:

    Jawr will try its best to force your clients to cache the resources. If a browser asks if a file changed, a 304 (not modified) header is sent back with no content. On the other hand, with Jawr you will be 100% sure that new versions of your bundles are downloaded by all clients. Every URL to your resources will include an automatically generated, content-based prefix that changes automatically whenever a resurce is updated. Once you deploy a new version, the URL to the bundle will change as well so it will be impossible that a client uses an older, cached version.

    该库也会进行JS/CSS缩小,但如果不想这样做,可以将其关闭。


    仅为纯JS中的本地开发禁用script.js的缓存

    注入random script.js?wizardry=1231234和blocks regular script.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <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 type="text/javascript" src="scripts.js">


    "SCDF建议的另一个想法是在文件中附加一个伪造的查询字符串。(一些自动使用时间戳作为伪查询字符串的python代码是由pi提交的。)但是,对于浏览器是否会缓存带有查询字符串的文件有一些讨论。(记住,我们希望浏览器缓存该文件并在以后访问时使用它。我们只希望它在文件发生更改时再次获取该文件。)由于不清楚虚假查询字符串会发生什么情况,因此我不接受该答案。"

    >

    散列文件意味着当文件发生更改时,查询字符串将发生更改。如果没有,它将保持不变。每个会话也强制重新加载。

    或者,您也可以使用重写使浏览器认为它是一个新的URI。


    我在为我的SPA寻找解决方案时遇到了这个问题,它只有一个index.html,列出了所有必要的文件。虽然我得到了一些帮助我的线索,但我找不到一个快速而简单的解决方案。

    最后,我编写了一个快速页面(包括所有代码),作为发布过程的一部分,它是自动版本html/js index.html所必需的。它工作得很好,只根据上次修改的日期更新新文件。

    你可以在http://blueskycont.com/wp/2016/05/12/autoversion your spa index html/上看到我的帖子。那里也有一个免费工作的winapp。

    代码的核心是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
           private void ParseIndex(string inFile, string addPath, string outFile)
        {
            string path = Path.GetDirectoryName(inFile);
            HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
            document.Load(inFile);
            foreach (HtmlNode link in document.DocumentNode.Descendants("script"))
            {
                if (link.Attributes["src"]!=null)
                {
                    resetQueryString(path, addPath, link,"src");
                }
            }
            foreach (HtmlNode link in document.DocumentNode.Descendants("link"))
            {
                if (link.Attributes["href"] != null && link.Attributes["type"] != null)
                {
                    if (link.Attributes["type"].Value =="text/css" || link.Attributes["type"].Value =="text/html")
                    {
                        resetQueryString(path, addPath, link,"href");
                    }
                }
            }
            document.Save(outFile);
            MessageBox.Show("Your file has been processed.","Autoversion complete");
        }

        private void resetQueryString(string path, string addPath, HtmlNode link, string attrType)
        {
            string currFileName = link.Attributes[attrType].Value;

            string uripath = currFileName;
            if (currFileName.Contains('?')) uripath = currFileName.Substring(0, currFileName.IndexOf('?'));
            string baseFile = Path.Combine(path, uripath);
            if (!File.Exists(baseFile)) baseFile = Path.Combine(addPath, uripath);
            if (!File.Exists(baseFile)) return;
            DateTime lastModified = System.IO.File.GetLastWriteTime(baseFile);
            link.Attributes[attrType].Value = uripath +"?v=" + lastModified.ToString("yyyyMMddhhmm");
        }

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

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

    例如,这里有一个简单的bash脚本,它从stdin中读取文件名列表,并将包含哈希的JSON文件写入stdout:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash
    # create a json map from filenames to md5s
    # run as hashes.sh < inputfile.list > outputfile.json

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

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


    ASP.NET网站的另一个建议,

  • 为不同的静态文件设置不同的缓存控制:最大期限值。
  • 对于CSS/JS文件,在服务器上修改这些文件的可能性很高,因此请设置一个最小的缓存控制:最大期限值为1或2分钟,或者满足您的需要。
  • 对于图像,将far-date设置为cache-control:max-age值,例如360天。
  • 通过这样做,当我们发出第一个请求时,所有静态内容都会下载到客户机上,并得到200-OK的响应。
  • 在随后的请求和两分钟后,我们看到在CSS和JS文件上有304个未修改的请求,这避免了我们进行CSS/JS版本控制。
  • 在缓存过期之前,不会请求图像文件,因为它们将从缓存内存中使用。
  • 通过使用下面的web.config配置,我们可以实现上述行为,

  • 我发现在资源URL中使用基于时间戳或哈希的区分符的方法有问题,在服务器上请求时,该区分符会被去掉。包含链接(例如样式表)的页面也可能被缓存。因此,缓存的页面可能请求样式表的旧版本,但将提供最新版本,这可能与请求的页面一起工作,也可能不工作。

    要解决这个问题,您要么使用no-cache头或meta保护请求页面,以确保在每次加载时刷新它。或者,您必须维护您在服务器上部署的样式文件的所有版本,每个版本都作为一个单独的文件,并且它们的区分符保持不变,这样请求页面就可以获得它设计的样式文件的版本。在后一种情况下,您基本上将HTML页面和样式表的版本绑定在一起,这可以静态完成,不需要任何服务器逻辑。


    我的方法是简单地将link元素放入服务器端,包括:

    1
    <!--#include virtual="/includes/css-element.txt"-->

    其中css-element.txt的内容是

    1
    <link rel="stylesheet" href="mycss.css"/>

    所以当你想链接到my-new-css.css或其他什么东西的时候,你只需更改include。


    好吧,每次页面加载时,我都会通过向JS文件版本中添加一个随机数字来更改JS版本,如下所示:

    1
    2
    3
    4
    5
    // Add it to the top of the page
    <?php
    srand();
    $random_number = rand();
    ?>

    然后将随机数应用到JS版本,如下所示:

    1
    <script src="file.js?version=<?php echo $random_number;?>">


    现有答案的微小改进…

    使用随机数或会话ID将导致每次请求时重新加载。理想情况下,只有在任何JS/CSS文件中进行了一些代码更改时,我们才需要进行更改。当使用公共JSP文件作为许多其他JSP和JS文件的模板时在公共JSP文件中添加以下内容

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

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

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

    优势:

    1) This approach will help you in changing version number at one location only.

    2) Maintaining proper version number(Usually Build/release number) will help you to check/verify your code changes being deployed properly.(From developer console of the browser.

    另一个有用的提示:

    如果您使用的是chrome浏览器,则可以在dev工具打开时禁用缓存。在chrome中点击f12>f1滚动到设置>首选项>网络>禁用缓存(devtools打开时)

    Chrome DevTools


    One of the best and quick approach I know is to change the name of the folder where you
    have CSS or JS Files.
    OR for Developers.
    Change the name of your CSS/js files something like versions.

    1
    <link rel="stylesheet" href="cssfolder/somecssfile-ver-1.css"/>

    Do same for your js files.


    可以使用SRI中断浏览器缓存。每次只需要用新的SRI哈希更新index.html。当浏览器加载HTML并发现HTML页面上的SRI哈希与资源的缓存版本不匹配时,它将从服务器重新加载资源。它还具有绕过跨源读取阻塞的良好副作用。


    如果您使用jquery,有一个名为cache的选项将附加一个随机数我知道这不是一个完整的答案,但它可能会节省你一些时间


    我没有看到JS文件中提到的另一种方法是将jquery $.getScript$.ajaxSetup选项cache: false结合使用。

    而不是:

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

    你可以使用:

    1
    2
    3
    4
    5
    $.ajaxSetup({
      cache: false
    });

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


    最简单的方法是利用PHP文件读取功能。只需让PHP将文件的内容回送到标记中即可。

    1
    2
    3
    4
    <?php
    //Replace the 'style.css' with the link to the stylesheet.
    echo"<style type='text/css'>".file_get_contents('style.css')."</style>";
    ?>

    如果您使用的是除PHP之外的其他语言,那么根据语言的不同会有一些变化,但是几乎所有语言都有打印文件内容的方法。把它放在正确的位置(在部分中),这样,你就不必依赖浏览器了。


    更改文件名将有效。但这通常不是最简单的解决方案。

    正如您所注意到的,"无缓存"的HTTP缓存控制头并不总是有效。HTTP1.1规范允许用户代理有空间决定是否请求新的拷贝。(如果只看指令的名称,这是不直观的。阅读缓存的实际HTTP 1.1规范…这在上下文中有点意义。)

    简而言之,如果您想要铁紧缓存控制,请使用

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

    在您的响应头中。