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

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

问题是:强制用户浏览器在文件更改后重新加载文件的最优雅方法是什么?

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

更新:

经过一段时间的讨论后,我发现John Millikinda5id的建议很有用。事实证明有一个术语:自动版本化

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

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

由于尚不清楚假查询字符串会发生什么,因此我不接受该答案。

Tony飞云2020/03/15 18:53:49

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

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

如果您使用的是ASP.NET MVC,则可以在此处查看其他答案中的代码

NearPro2020/03/15 18:53:49

我建议执行以下过程:

  • 每次部署时都要对css / js文件进行版本控制,例如:screen.1233.css(如果使用版本控制系统,则该数字可以是SVN修订版)

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

蛋蛋Davaid十三2020/03/15 18:53:49

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

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

通过向您的项目添加扩展方法来扩展Page:

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;
    }
}
Tony飞云2020/03/15 18:53:49

只需在您要进行硬重装的地方添加此代码(强制浏览器重载已缓存的CSS / JS文件) ,就可以在.load内执行此操作,这样它就不会像循环那样刷新

 $( window ).load(function() {
   location.reload(true);
});
达蒙樱2020/03/15 18:53:49

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

浏览器应该通过读取Web服务器的响应(尤其是http标头)来充分了解要缓存的内容和不缓存的内容,该资源有效期为多长时间?自上次检索以来,此资源是否已更新?等。

如果配置正确,则仅在更新应用程序文件时(在某个时候)应刷新浏览器缓存。例如,您可以将Web服务器配置为告诉浏览器从不缓存文件(这是一个坏主意)。

https://www.mnot.net/cache_docs/#WORK上有更深入的解释

凯JinJin2020/03/15 18:53:49

对于ASP.NET,我想使用高级选项(调试/发布模式,版本)的下一个解决方案:

通过以下方式包含的Js或Css文件:

<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中的以下方式计算的:

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();
    }
    ...
}
阳光小胖2020/03/15 18:53:49

您可以简单地在CSS / JS网址中添加一些随机数,例如

example.css?randomNo=Math.random()
蛋蛋Itachi2020/03/15 18:53:48

如果将session-id添加为js / css文件的适当参数,则可以强制执行“整个会话范围的缓存”:

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

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

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
Stafan小哥2020/03/15 18:53:48

谷歌浏览器具有“ 硬重装 ”以及“ 清空缓存和硬重装”选项。您可以单击并按住“重装”按钮(在检查模式下)以选择一个。

理查德Tom2020/03/15 18:53:48

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

https://developer.chrome.com/devtools/docs/tips-and-tricks#hard-reload

在开发人员工具打开的情况下,只需长按刷新按钮,然后将鼠标悬停在“空缓存和硬重载”上就可以释放。

这是我最好的朋友,并且是获得您想要的东西的超轻量级方法!

十三卡卡西2020/03/15 18:53:48

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

这将导致以下结果:

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

当然,在编辑CSS文件的情况下,datetime解决方案也能完成工作,但是我认为这与css文件的内容有关,与文件datetime无关,那么为什么要混在一起呢?

SamGreen2020/03/15 18:53:48

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

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

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

Itachi猪猪Green2020/03/15 18:53:48

RewriteRule需要对js或css文件进行小的更新,该文件的末尾包含点符号版本。例如json-1.3.js。

我在正则表达式中添加了点否定类[^。],因此.number。被忽略。

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
ProJinJin樱2020/03/15 18:53:48

不要使用foo.css?version = 1!浏览器不应该缓存带有GET变量的URL。根据http://www.thinkvitamin.com/features/webapps/serving-javascript-fast的说法,尽管IE和Firefox忽略了这一点,但Opera和Safari却没有!相反,请使用foo.v1234.css,并使用重写规则删除版本号。

小宇宙飞云2020/03/15 18:53:48

现有的30多个答案对于大约2008年的网站来说是不错的建议。但是,当涉及到现代的单页应用程序(SPA)时,可能是时候重新考虑一些基本假设了……尤其是这样的想法,即Web服务器只需要提供一个最新的版本即可。文件。

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

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

如果/some.template在版本M版本N之间更改了格式(或文件已重命名或其他名称),则您可能不希望将模板的版本N发送到运行解析器旧版本M的浏览器

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

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

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

  1. 安装所有站点文件到版本迪尔斯:/v<release_tag_1>/…files…/v<release_tag_2>/…files…
  2. 设置HTTP标头,以使浏览器永远缓存文件
    • (或者更好的是,将所有内容都放入CDN中)
  3. 更新全部<script><link>标签等以指向版本号目录之一中的该文件

That last step sounds tricky, as it could require calling a URL builder for every URL in your server-side or client-side code. Or you could just make clever use of the <base> tag and change the current version in one place.

† One way around this is to be aggressive about forcing the browser to reload everything when a new version is released. But for the sake of letting any in-progress operations to complete, it may still be easiest to support at least two versions in parallel: v-current and v-previous.

飞云Tony2020/03/15 18:53:48

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

也可以看看:

猴子L2020/03/15 18:53:47

您只需将?foo=1234css / js导入的末尾放到1234即可。请看SO html源代码示例。

The idea there being that the ? parameters are discarded / ignored on the request anyway and you can change that number when you roll out a new version.


Note: There is some argument with regard to exactly how this affects caching. I believe the general gist of it is that GET requests, with or without parameters should be cachable, so the above solution should work.

However, it is down to both the web server to decide if it wants to adhere to that part of the spec and the browser the user uses, as it can just go right ahead and ask for a fresh version anyway.

小宇宙达蒙2020/03/15 18:53:47

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

因此,您的网址将类似于

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

您仍然可以使用重写规则来去除哈希,但是好处是现在您可以将缓存策略设置为“永远缓存”,因为如果URL相同,则意味着文件未更改。

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

只要每次CSS更改就运行该脚本,您就很好。更改后,浏览器将仅重新加载文件。如果您进行了修改然后撤消了修改,那么就可以轻松弄清楚您需要返回哪个版本才能使访问者不必重新下载。

伽罗达蒙小胖2020/03/15 18:53:47

Google的针对Apache mod_pagespeed插件将为您完成自动版本控制。真的很滑。

它以离开网络服务器的方式解析HTML(可用于PHP,rails,python,静态HTML等等),并重写指向CSS,JS,图像文件的链接,以便它们包含id代码。它将修改后的URL上的文件提供给文件,并对其进行非常长的缓存控制。文件更改时,它会自动更改URL,因此浏览器必须重新获取它们。它基本上可以正常工作,而无需更改您的代码。它甚至还会使您的代码最小化。