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

1150
我注意到一些浏览器(尤其是Opera和Firefox)在使用缓存的.css.js文件时非常热衷,甚至在浏览器会话之间也是如此。当您更新其中一个文件但用户的浏览器继续使用缓存副本时,这会导致问题。
强制用户的浏览器在文件更改后重新加载文件的最优雅方法是什么?
理想情况下,解决方案不会在每次访问页面时都强制浏览器重新加载文件。

我发现John Millikinda5id的建议很有用。原来这个术语叫做自动版本控制

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

另一个想法是由SCdF提出的,即将虚假查询字符串附加到文件中。(一些Python代码,可以自动使用时间戳作为虚假查询字符串,被pi.提交。)

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


我在我的 .htaccess 文件中有这个设置,从未出现过缓存文件的问题:ExpiresActive On ExpiresDefault "modification" - Frank Conijn - Support Ukraine
2
我绝对同意将版本信息添加到文件的URL中是迄今为止最好的方法。 它可以始终为每个人正常工作。 但是,如果您没有使用它,并且只需要偶尔重新加载一个CSS或JS文件以在自己的浏览器中运行...只需在其自己的选项卡中打开它,然后按SHIFT-reload(或CTRL-F5)! 您可以通过在(隐藏的)iframe中加载文件,等待加载完成,然后调用 iframe.contentWindow.location.reload(true) 来有效地执行相同的操作。请参见 https://dev59.com/6XNA5IYBdhLWcg3wIqPr#22429796 的方法(4)-那是关于图片的,但同样适用。 - Doin
6
我很欣赏这个问题的提问方式,也很感谢提问者对问题进行了更新。它完整地描述了我应该在回答中期待什么。从现在开始,我会采用这种方法来提问。干杯! - rd22
2
参考:da5id's's deleted answer 是这样说的:“如果更新足够大/重要,我通常会更改文件的名称。”。 - Peter Mortensen
如果更改不是很频繁,我有一个建议。只需更改文件名并编辑源代码以包含新的文件名。然后浏览器就没有缓存文件可读取。 - SK-the-Learner
我写了一篇关于使用GitHub进行CDN、自动版本控制、自动更新等方面的方法的文章,这个方法真的很有效。https://dany1980.medium.com/cdn-e-aggiornamenti-automatici-degli-assets-che-restano-in-cache-d362a99f054d(这是从意大利语翻译过来的) - Daniele Rugginenti
58个回答

7

假设你有一个文件可用于:

/styles/screen.css

您可以在URI上附加一个包含版本信息的查询参数,例如:
/styles/screen.css?v=1234

或者您可以在前面添加版本信息,例如:

/v/1234/styles/screen.css

在我看来,第二种方法更适用于CSS文件,因为它们可以使用相对URL引用图像,这意味着如果您像这样指定background-image:
body {
    background-image: url('images/happy.gif');
}

它的URL将有效地是:

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

这意味着如果您更新所使用的版本号,服务器将把其视为新资源并不使用缓存版本。如果您基于Subversion, CVS等版本进行版本控制,则CSS文件中引用的图像更改将被注意到。第一种方案不能保证这一点,即相对于/styles/screen.css?v=1235的URL images/happy.gif/styles/images/happy.gif,其中不包含任何版本信息。

我使用Java servlets实现了这种技术来实现缓存解决方案,并简单地处理对/v/*的请求,委托给底层资源(即/styles/screen.css)。在开发模式下,我设置了缓存头,告诉客户端始终检查资源与服务器的新鲜程度(如果您委托给Tomcat的DefaultServlet并且.css.js等文件未更改,则通常会导致304),而在部署模式下,我设置了永久缓存的标头。


如果您只使用相对URL,则可以简单地添加一个文件夹,需要时可以重命名它。然后,您需要确保从基本文件夹重定向到正确的文件夹,例如在PHP中:<?php header( 'Location: folder1/login.phtml' ); ?> - Gruber
2
使用第二种方法,CSS 的更改将使所有使用相对 URL 引用的图像的缓存副本无效,这可能是需要的,也可能不是。 - TomG

6
如果您将会话ID作为JavaScript/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>

6
对于ASP.NET,我提议采用以下高级选项解决方案(调试/发布模式、版本):
可以按以下方式引入JavaScript或CSS文件:
<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

Global.JsPostfixGlobal.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();
    }
    ...
}

5

只需在需要进行强制刷新(强制浏览器重新加载缓存的CSS和JavaScript文件)的位置添加以下代码:

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

请在.load内执行此操作,以避免像循环一样刷新。

1
不适用于 Chrome。仍从磁盘缓存加载资源。 - Jason Kim

5

如果你在使用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;

5

对于开发:使用浏览器设置,例如,Chrome网络选项卡中有一个禁用缓存选项。

对于生产:使用服务器端渲染框架或纯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>

这个例子需要编辑。这个想法很好,但是上面的开始和结束脚本标签有些混淆。 - Magnus

4

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

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

4

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

暂时移除缓存。

"使缓存与文件保持一致"..这太麻烦了...

一般来说,我并不介意在大多数项目上加载更多 - 即使是重新加载未更改的文件也不会有很大影响。 在开发应用程序时,我们大多数情况下都是从磁盘上加载,在 localhost:port 上 - 所以这个增加网络流量问题并不是一个完全无法解决的问题。

大多数小型项目只是玩玩 - 它们永远不会进入生产环境。 因此,对于它们,您不需要任何其他东西...

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

如何强制Chrome重新加载缓存文件

如果您在使用 Firefox 时出现缓存问题:

如何在Firefox上强制重新加载资源

在开发中如何在Firefox上禁用缓存

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

是的,这些信息已经包含在之前的答案中,但我仍然需要通过Google搜索才能找到它们。


1
OP问了一些问题,但回答的与此不同。这不是关于在本地强制加载,而是在生产环境中,你不能要求最终用户按照上述方法禁用缓存等。 - Jitendra Pancholi
1
嗨,如果它能正常工作那就太好了,但出于某些原因它并没有起作用...我在使用火狐浏览器,并且勾选此选项并不能防止火狐浏览器无法看到HTML中的最新更改(但在新的私人窗口中打开可以,但这不是一个合适的工作流程)。你有什么想法吗? - hugogogo
如果有人因为上面的解决方案看起来很好但不起作用而来到这个评论:我注意到像Ctrl-R或f5这样的快捷键并不足以完全重新加载页面,但是将焦点放在地址栏中(Ctrl_L或通过单击它),然后按Enter键可以工作,或者更简单的方法是:Ctrl-Shift-R(它可以工作,无论是否激活了开发工具箱中的此选项,因此实际上不是这个答案的解决方案,这不是对op问题的答案,对此混乱感到抱歉)。 - hugogogo

4

这里所有的答案似乎都建议在命名方案中使用某种形式的版本控制,这有其不足之处。

浏览器应该通过读取Web服务器的响应,特别是HTTP标头,来了解什么需要缓存,什么不需要缓存 - 这个资源的有效期是多长时间?自从我上次检索它以来,这个资源是否已更新等等。

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

关于如何工作的更深入的解释,请参见Web缓存工作原理


3
你可以使用SRI来打破浏览器缓存。每次只需更新index.html文件中的新SRI哈希值即可。当浏览器加载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>

哪些浏览器(包括版本)支持这个?请通过更新您的答案来回复(不要在评论中回复)。 - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接