【问题标题】:How to force the browser to reload cached CSS and JavaScript files如何强制浏览器重新加载缓存的 CSS 和 JavaScript 文件
【发布时间】:2010-09-12 05:51:56
【问题描述】:

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

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

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


我发现John Millikin'sda5id's 的建议很有用。事实证明有一个术语:auto-versioning

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

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

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

【问题讨论】:

  • 我的 .htaccess 中有这个,缓存文件从来没有任何问题:ExpiresActive On ExpiresDefault "modification".
  • 我绝对同意将版本信息添加到文件的 URL 是迄今为止最好的方法。它始终对每个人都有效。但是,如果你不使用它,你只需要偶尔在你自己的浏览器中重新加载一个 CSS 或 JS 文件......只需在它自己的选项卡中打开它并点击 SHIFT-reload(或 CTRL-F5)!您可以使用 JS 通过在(隐藏的)iframe 中加载文件,等待它加载,然后调用 iframe.contentWindow.location.reload(true) 来有效地做同样的事情。请参阅stackoverflow.com/a/22429796/999120 的方法 (4) - 这是关于图像的,但同样适用。
  • 我非常感谢提出这个问题的方式,并且从那时起一直在更新。它完全描述了我对答案的期望。从现在开始,我将在我的问题中遵循这种方法。干杯!
  • 供参考:da5id's's deleted answer“如果更新足够大/足够重要,我通常会更改文件名。”.
  • 如果更改不是很频繁,我有一个建议。只需更改文件名并编辑源代码以包含新文件名。那么就没有缓存文件供浏览器读取了。

标签: javascript css caching auto-versioning


【解决方案1】:

如果您不希望客户端缓存文件,此解决方案似乎是最快实施的。如果您使用 time() 调整部分,例如在footer.php中加载文件:

<script src="<?php echo get_template_directory_uri(); ?>/js/main.js?v=<?= time() ?>"></script>

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • os.stat() 会不会造成瓶颈?
    • @Richard stat 如果磁盘非常慢并且请求非常多,可能会成为瓶颈。在这种情况下,您可以将时间戳缓存在内存中的某个位置,并在每次新部署时清除此缓存。然而,在大多数用例中,这种复杂性并不是必需的。
    • 我知道这很古老,但对于任何阅读的人来说,时间戳都太激进了。这意味着您根本没有任何缓存,如果您需要,您可以使用静态文件的自定义标头来管理它。
    • @LarryBud:这是文件的时间戳,而不是当前时间戳。你肯定会有缓存。
    【解决方案3】:

    我将文件内容的 MD5 哈希值放在其 URL 中。这样我可以设置一个很长的过期日期,而不必担心用户有旧的 JS 或 CSS。

    我还在运行时(或在文件系统更改时)对每个文件计算一次,因此在设计时或构建过程中没有什么可笑的。

    如果您使用的是 ASP.NET MVC,那么您可以查看代码 in my other answer here

    【讨论】:

      【解决方案4】:

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

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

      • 我正在运行一个单页应用程序(用 Vue.JS 编写)。
      • 应用程序的输出由npm构建,并存储在dist文件夹中(重要文件为:dist/static/js/app.my_rand.js)
      • Nginx 负责提供这个 dist 文件夹中的内容,它会根据修改时间和 dist 文件夹的内容生成一个新的 Etag 值,这是某种指纹。因此,当资源发生变化时,会生成一个新的 Etag 值。
      • 当浏览器请求资源时,请求标头和存储的 Etag 之间的比较,可以确定资源的两种表示是否相同,并且可以从缓存中提供服务,或者需要具有新 Etag 的新响应送达。

      【讨论】:

      • 你能详细说明一下吗?现在它实际上是一个仅链接的答案。
      【解决方案5】:

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

      https://superuser.com/a/512833

      打开开发人员工具后,只需长按刷新按钮,然后将鼠标悬停在“空缓存和硬重新加载”上即可。

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

      【讨论】:

      • 如果您使用 Chrome 作为开发环境,另一种非侵入式解决方案是禁用缓存:在设置 cog 下,您可以通过选择“禁用缓存”使磁盘缓存无效(注意: DevTools 必须可见/打开才能正常工作)。
      • 什么是“长按”
      • 链接(实际上)已损坏。它重定向到通用页面 "Chrome DevTools" - developers.google.com/web/tools/chrome-devtools
      • @PeterMortensen 点击并按住点击按钮。
      • 为什么不直接按 ctrl+F5?
      【解决方案6】:

      这是一个纯 JavaScript 解决方案

      (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) 强制从服务器刷新页面。

      我通常将此作为&lt;head&gt; 中的第一个脚本,因此在加载任何其他内容之前对其进行评估。如果需要重新加载,用户几乎察觉不到。

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

      【讨论】:

      • 我尝试过这样的事情,这仅适用于重新加载的页面,但如果网站有多个页面共享相同的 css/图像,那么其他页面仍将使用旧资源。
      【解决方案7】:

      在 ASP.NET Core 中,您可以通过添加“asp-append-version”来实现:

      <link rel="stylesheet" href="~/css/xxx.css" asp-append-version="true" />
      
       <script src="~/js/xxx.js" asp-append-version="true"></script>
      

      它会生成 HTML:

      <link rel="stylesheet" href="/css/xxx.css?v=rwgRWCjxemznsx7wgNx5PbMO1EictA4Dd0SjiW0S90g" />
      

      每次更新文件时,框架都会生成一个新的版本号。

      【讨论】:

        【解决方案8】:

        这是我的基于 Bash 脚本的缓存清除解决方案:

        1. 我假设您的 index.html 文件中引用了 CSS 和 JavaScript 文件
        2. index.html 中添加时间戳作为 .js 和 .css 的参数,如下所示(仅限一次)
        3. 使用上述时间戳创建一个 timestamp.txt 文件。
        4. 对 .css 或 .js 文件进行任何更新后,只需运行以下 .sh 脚本

        带有时间戳的 .js 和 .css 的示例 index.html 条目:

        <link rel="stylesheet" href="bla_bla.css?v=my_timestamp">
        <script src="scripts/bla_bla.js?v=my_timestamp"></script>
        

        文件timestamp.txt应该只包含相同的时间戳'my_timestamp'(稍后会被脚本搜索和替换)

        最后是脚本(我们称之为 cache_buster.sh :D)

        old_timestamp=$(cat timestamp.txt)
        current_timestamp=$(date +%s)
        sed -i -e "s/$old_timestamp/$current_timestamp/g" index.html
        echo "$current_timestamp" >timestamp.txt
        

        (Visual Studio Code 用户)您可以将此脚本放在一个挂钩中,这样每次在您的工作区中保存文件时都会调用它。

        【讨论】:

          【解决方案9】:

          静态文件的简单解决方案(仅用于开发目的),使用脚本标记注入向脚本 URI 添加随机版本号

          <script>
              var script = document.createElement('script');
              script.src = "js/app.js?v=" + Math.random();
              document.getElementsByTagName('head')[0].appendChild(script);
          </script>
          

          【讨论】:

            【解决方案10】:

            我们有一种解决方案,采用不同的实施方式。我们使用上面的解决方案。

            datatables?v=1
            

            我们可以处理文件的版本。这意味着每次我们更改文件时,也要更改它的版本。但这不是一种合适的方式。

            另一种方式是使用GUID。它也不适合,因为每次它都从浏览器缓存中获取文件并且不使用。

            datatables?v=Guid.NewGuid()
            

            最后一种最好的方法是:

            发生文件更改时,也要更改版本。检查以下代码:

            <script src="~/scripts/main.js?v=@File.GetLastWriteTime(Server.MapPath("/scripts/main.js")).ToString("yyyyMMddHHmmss")"></script>
            

            这样,当你改变文件时,LastWriteTime 也会改变,所以文件的版本会改变,然后当你打开浏览器时,它会检测到一个新文件并获取它。

            【讨论】:

              【解决方案11】:

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

              暂时删除缓存。

              "keep caching consistent with the file" .. 太麻烦了..

              一般来说,我不介意加载更多 - 甚至再次加载未更改的文件 - 在大多数项目中 - 实际上是无关紧要的。在开发应用程序时 - 我们主要从磁盘加载,在 localhost:port 上 - 所以这个 increase in network traffic 问题不是一个破坏交易的问题

              大多数小型项目只是在玩——它们永远不会最终投入生产。所以对他们来说,你不需要更多...

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

              如果您有 Firefox 缓存问题:

              仅在开发中执行此操作。您还需要一种机制来强制重新加载以进行生产,因为如果您经常更新应用程序并且您没有像上面的答案中描述的那样提供专用的缓存同步机制,您的用户将使用旧的缓存失效模块。

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

              【讨论】:

              • OP 提出了一些问题并回答了一些问题。这不是在本地强制加载,而是在生产中,你不能要求最终用户按照上面的操作来禁用缓存等。
              【解决方案12】:

              对于开发:使用浏览器设置:例如,Chrome network tab 有一个 disable cache 选项。

              对于生产:使用服务器端渲染框架或纯 JavaScript 代码将唯一的查询参数附加到请求(例如q?Date.now())。

              // Pure JavaScript unique query parameter generation
              //
              //=== myfile.js
              
              function hello() { console.log('hello') };
              
              //=== end of file
              
              <script type="text/javascript">
                  document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
                  // document.write is considered bad practice!
                  // We can't use hello() yet
              </script>')
              
              <script type="text/javascript">
                  hello();
              </script>
              

              【讨论】:

              • 这个例子需要编辑。想法不错,但是上面的开始和结束脚本标签有混淆。
              【解决方案13】:

              您可以使用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>
              

              【讨论】:

              • 什么浏览器,包括。版本,支持吗?通过更新您的答案来回应(不在 cmets 中)。
              【解决方案14】:

              我所知道的最好和最快的方法之一是更改您拥有 CSS 或 JavaScript 文件的文件夹的名称。

              或者对于开发人员:更改 CSS 和 JavaScript 文件的名称,例如版本。

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

              对您的 JavaScript 文件执行相同的操作。

              【讨论】:

                【解决方案15】:

                现有答案的小改进...

                使用随机数或会话 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>
                

                优点:

                1. 此方法将帮助您仅在一个位置更改版本号。
                1. 维护正确的版本号(通常是内部版本号/发布号)将帮助您检查/验证您的代码更改是否正确部署(从浏览器的开发者控制台)。

                另一个有用的提示:

                如果您使用的是 Chrome 浏览器,您可以在 开发工具 打开时禁用缓存。 在 Chrome 中,点击 F12F1 并滚动到 SettingsPreferencesNetwork → *禁用缓存(DevTools 打开时)

                【讨论】:

                  【解决方案16】:

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

                  在 ASP.NET 中:

                  <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]", ""))"></script>
                  

                  这可以简化为:

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

                  通过在你的项目中添加扩展方法来扩展页面

                  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;
                      }
                  }
                  

                  【讨论】:

                    【解决方案17】:

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

                    【讨论】:

                      【解决方案18】:

                      谷歌浏览器有 Hard Reload 以及 Empty Cache and Hard Reload 选项。您可以单击并按住重新加载按钮(在检查模式中)选择一个。

                      【讨论】:

                      • 为了澄清,通过“检查模式”,他们指的是“开发工具”又名 F12,又名 ctrl+shift+i,又名ant menu > More Tools > Developer Tools,又名@ 987654324@ > Inspect Element。还有一个隐藏在开发工具中某处的设置(我忘记了位置),每次重新加载时都会重新加载。
                      【解决方案19】:

                      只需将此代码添加到您要进行硬重新加载的位置(强制浏览器重新加载缓存的 CSS 和 JavaScript 文件):

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

                      .load 内执行此操作,这样它就不会像循环一样刷新。

                      【讨论】:

                      • 不适用于 Chrome。仍在从磁盘缓存加载资产
                      【解决方案20】:

                      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 输出(filemtimeUnix timestamp 形式返回时间)

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

                      【讨论】:

                      • 这个命令在 html 中的输出是什么?如果我只需要更新 ?v=3、?v=4 等版本怎么办? - 每次用户进入网站时都不会强制浏览器加载 css
                      • filemtime: "该函数返回文件数据块被写入的时间,即文件内容发生变化的时间。"来源:php.net/manual/en/function.filemtime.php
                      【解决方案21】:

                      如果您使用Git 和 PHP,则可以在每次 Git 存储库发生更改时从缓存中重新加载脚本,使用以下代码:

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

                      【讨论】:

                        【解决方案22】:

                        好吧,我已经通过在每次页面加载时通过向 JavaScript 文件版本添加随机数来更改 JavaScript 文件版本来使其工作,如下所示:

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

                        然后将随机数应用到 JavaScript 版本如下:

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

                        【讨论】:

                        • 这是一个非常糟糕的主意。这意味着用户必须在每次加载页面时重新下载文件。缓存是一件好事——这个问题是关于如何在需要的时候使用缓存,而不是在不需要的时候。
                        • 是的,用户必须在每次加载页面时重新下载文件,因为 (iframe) 下包含的某些 java 脚本不会更新页面内容,除非用户按 F5 或手动重新加载页面。因此,这是每次新访问者访问网站时重新加载内容的最佳解决方案。
                        【解决方案23】:

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

                        浏览器应该通过读取 Web 服务器的响应,特别是 HTTP 标头,清楚地知道哪些内容可以缓存,哪些内容不可以缓存——该资源的有效期是多久?自从我上次检索后,此资源是否已更新?等等

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

                        How Web Caches Work 中对它的工作原理进行了更深入的解释。

                        【讨论】:

                          【解决方案24】:

                          我还没有找到动态创建脚本节点(或 CSS)元素的客户端 DOM 方法:

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

                          【讨论】:

                          • 你发现了什么?你能说得更清楚一点吗?最好是editing your answer(但没有“编辑:”,“更新:”或类似的),而不是在cmets中。
                          【解决方案25】:

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

                          最后,我编写了一个快速页面(包括所有代码),作为发布过程的一部分,它可以自动转换 HTML/JavaScript index.html 文件。它完美运行,仅根据上次修改日期更新新文件。

                          您可以在 Autoversion your SPA index.html 看到我的帖子。那里也有一个独立的 Windows 应用程序。

                          代码的核心是:

                          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");
                          }
                          

                          【讨论】:

                            【解决方案26】:

                            现有的 30 个左右的答案对于大约 2008 年的网站来说是很好的建议。但是,当涉及到现代的 single-page application (SPA) 时,可能是时候重新考虑一些基本假设了……特别是希望 Web 服务器只为单个服务器提供服务的想法,文件的最新版本。

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

                            1. 您的CD 管道将应用程序的新版本N 部署到服务器上
                            2. 您在 SPA 中导航,该 SPA 向服务器发送 XMLHttpRequest (XHR) 以获取 /some.template
                            • (您的浏览器尚未刷新页面,所以您仍在运行版本M
                            1. 服务器以 /some.template 的内容进行响应——您希望它返回模板的版本 M 还是 N

                            如果/some.template 的格式在版本MN 之间发生变化(或者文件被重命名或其他)你可能不想要版本N 个模板发送到运行旧版本解析器的浏览器 M。†

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

                            • 在初始页面加载后的某个时间异步请求资源
                            • 应用程序逻辑假设资源内容(在未来版本中可能会发生变化)

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

                            1. 将所有站点文件安装到版本化目录中:/v&lt;release_tag_1&gt;/…files…/v&lt;release_tag_2&gt;/…files…
                            2. 设置 HTTP 标头以让浏览器永久缓存文件
                            • (或者更好的是,将所有内容都放在 CDN 中)
                            1. 更新所有&lt;script&gt;&lt;link&gt; 标记等,以指向版本控制目录之一中的该文件

                            最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个 URL 调用 URL 构建器。或者您可以巧妙地使用&lt;base&gt; tag 并在一处更改当前版本。

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

                            【讨论】:

                            • Michael - 您的评论非常相关。我来到这里正是想为我的 SPA 找到解决方案。我得到了一些指示,但必须自己想出一个解决方案。最后,我对自己的想法感到非常满意,所以我写了一篇博文并回答了这个问题(包括代码)。谢谢指点
                            • 很棒的评论。当人们一直在谈论缓存破坏和 HTTP 缓存作为网站缓存问题的真正解决方案而不评论 SPA 的新问题时,我无法理解,好像这是一个边缘案例。
                            • 出色的反应和绝对理想的策略!提及base 标签的奖励积分!至于支持旧代码:这并不总是可能的,也不是一个好主意。新版本的代码可能支持对应用程序的其他部分进行重大更改,或者可能涉及紧急修复、漏洞补丁等。我自己还没有实施这个策略,但我一直觉得整体架构应该允许部署将旧版本标记为 obsolete 并在下次进行异步调用时强制重新加载(或者只是强制取消所有身份验证)通过 WebSockets 会话)。
                            • 很高兴看到关于单页应用程序的深思熟虑的答案。
                            • 如果您想搜索更多信息,那就是“蓝绿部署”。
                            【解决方案27】:

                            通过阅读得出SilverStripe 的特定答案:http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110

                            希望这将有助于使用 SilverStripe 模板并尝试在每次页面访问/刷新时强制重新加载缓存图像的人。在我的情况下,它是一个 GIF 动画,它只播放一次,因此在缓存后没有重播。在我的模板中,我只是添加了:

                            ?$Now.Format(dmYHis)
                            

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

                            【讨论】:

                            • api.silverstripe.org 链接已损坏 (404)。
                            【解决方案28】:

                            这里的许多答案都主张在 URL 中添加时间戳。除非您直接修改生产文件,否则文件的时间戳不太可能反映文件更改的时间。在大多数情况下,这将导致 URL 比文件本身更频繁地更改。这就是为什么您应该按照levik and others 的建议使用文件内容的快速哈希,例如MD5

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

                            例如,下面是一个简单的 bash 脚本,它从 standard input 读取文件名列表并将包含哈希的 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 "}"
                            

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

                            【讨论】:

                              【解决方案29】:

                              对于 ASP.NET 4.5 及更高版本,您可以使用 script bundling

                              请求 http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 是针对捆绑包 AllMyScripts 并包含查询字符串对 v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81。查询字符串v 有一个值标记,它是用于缓存的唯一标识符。只要包没有改变,ASP.NET 应用程序就会使用这个令牌请求 AllMyScripts 包。如果包中的任何文件发生变化,ASP.NET 优化框架将生成一个新的令牌,保证浏览器对该包的请求将获得最新的包。

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

                              【讨论】:

                                【解决方案30】:

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

                                更多详情,请参见: Using the application cache

                                【讨论】:

                                  猜你喜欢
                                  • 2017-12-05
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多