如何强制客户端刷新JavaScript文件?

703
我们目前正在私人测试版中工作,因此仍在进行相当快速的更改过程,尽管显然随着使用量的增加,我们将减缓这个过程。话虽如此,我们遇到的一个问题是,在推出新的JavaScript文件更新后,客户端浏览器仍然使用缓存版本的文件,并且无法看到更新。显然,在支持电话中,我们可以简单地告诉他们执行ctrlF5刷新操作,以确保他们从服务器获取最新的文件,但最好在那之前处理这个问题。 我们目前的想法是简单地将版本号附加到JavaScript文件的名称上,然后在进行更改时递增脚本中的版本并更新所有引用。这确实可以完成工作,但每次发布都要更新引用可能会变得繁琐。 正如我相信我们不是第一个处理这个问题的人一样,我认为我会向社区提出这个问题。您是如何确保客户端在更新代码时更新其缓存的?如果您正在使用上述方法,您是否使用了简化变更的流程?

3
https://dev59.com/tnVD5IYBdhLWcg3wAWoO - understack
1
我已经尝试了下面所有的“答案”,它们实际上都是黑科技。没有一个可以从缓存中精确地删除图像。难怪这个问题在2022年5月28日获得了恰好666个赞。显然,这是一个浏览器仍然缺失的功能。服务器端的无缓存头似乎是唯一的方法。 - Avatar
1
有人尝试过这个吗? - S. Dre
我认为可以使用 ETag 头来在文件内容更改时更新缓存。 - Anderson Green
30个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
614

据我所知,一个常见的解决方案是在脚本的src链接中添加?<version>

例如:

<script type="text/javascript" src="myfile.js?1500"></script>
我假设现在没有比查找替换更好的方法来递增所有脚本标签中的“版本号”?你可以使用版本控制系统来完成这项工作。大多数版本控制系统都有自动注入修订号的方式,例如在提交时。它会类似于这样:
<script type="text/javascript" src="myfile.js?$$REVISION$$"></script>

当然,总会有更好的解决方案,比如这个


6
有人知道IE7是否会忽略这个吗?当我在IE8兼容性视图中测试时,它似乎忽略了附加的数据并使用了缓存文件。 - Shane Reustle
5
我一直知道查询字符串是键值对,例如 ?ver=123。谢谢! :) - Ankur-m
7
我认为这并不是关于版本号高低的问题,而是将追加变量的值更改为浏览器尚未缓存的内容。 - gherkins
54
注意:这被认为是一种黑客技巧。此方法通过欺骗浏览器来指定一个新文件,因为它只查看完整的文件名而不对其进行解释。 foo.js?1foo.js?2 不是相同的文件名,因此浏览器会认为它们是两个不同的文件。其中一个缺点是两个文件将同时存在于用户的缓存中,占用不必要的空间。 - Lee White
17
无论您如何解决该问题,这两个文件都将被缓存在浏览器中。这可能是因为它们具有不同的请求参数或不同的路径。因此,我认为请求参数方法并没有这方面的劣势。 - Planky
显示剩余10条评论

112

在URL末尾追加当前时间是一种常见的解决方法。但是,如果您想要的话,也可以在Web服务器级别上进行管理。服务器可以配置为为javascript文件发送不同的HTTP标题。

例如,要强制使文件缓存不超过1天,您可以发送:

Cache-Control: max-age=86400, must-revalidate

如果您想强制用户始终获取最新版本的 Beta 版本,则可以使用以下命令:

Cache-Control: no-cache, must-revalidate

5
请更具体些好吗? - Kreker
10
他在谈论Web服务器为每个文件发送的标头。例如,在Apache中应该可以配置这个。我认为这将是最好的方法。 - Pierre de LESPINAY
3
你在哪里配置这个? - Diego
2
对于开发 web 应用程序,这可能是一个不错的解决方案。但对于生产环境,如果你不想永久地使缓存失效,除非你确信每个目标客户端浏览器都已经访问了站点,否则这并不是一个好的解决方案。这让我想到潜在的 Web 服务器特性:根据配置的部署日期调整最大年龄参数。那将是很棒的。 - Claude Brisson
Chrome需要这些设置才能正确缓存。如果没有它们,Chrome将永久缓存文件。Mozilla使用更合理的默认设置。更多信息请参见:https://agiletribe.wordpress.com/2018/01/29/caching-for-chrome/ - AgilePro
显示剩余3条评论

46

Google Page-Speed: 静态资源的 URL 中不应包含查询字符串。 大多数代理服务器(尤其是 Squid 版本 3.0 及以下)即使响应头中有 Cache-control: public 标头,也不会缓存带有“?”的 URL 资源。为了让代理服务器缓存这些资源,请从静态资源的引用中删除查询字符串,并将参数编码到文件名中。

在这种情况下,您可以将版本号包含在 URL 中,例如:v1.2/script.js,并使用 Apache mod_rewrite 将链接重定向到 http://abc.com/script.js。当更改版本时,客户端浏览器将更新新文件。


我尝试了?的解决方案,在IE8中出现了JavaScript错误。Mod rewrite是一种选择,但在大多数情况下,我们对服务器没有那么多的控制权。我更喜欢将版本附加到js文件本身或为每个版本创建一个文件夹。 - Karthik Sankar
@Hắc Huyền Minh:但是当脚本需要重新加载时,不应该从代理缓存重新加载... - Stefan Steiger

37

加入文件大小作为加载参数怎么样?

<script type='text/javascript' src='path/to/file/mylibrary.js?filever=<?=filesize('path/to/file/mylibrary.js')?>'></script>

每次更新文件时,“filever”参数都会更改。

如果您更新文件并且更新导致文件大小相同,那么可能性是多少?


5
这里使用了 PHP 标签,如果使用 PHP 的话,这确实是一个好主意。 - Jerther
5
我认为添加修改日期比文件大小更好 :) - Mazz
6
我的初步想法是添加文件的哈希值而不是版本号。 - Mike Cheel
2
我认为如果添加一个Unix时间戳,它也可以工作,对吗?例如:‘...file.js?filever=<?=time()?>’ - garanda
4
使用filemtime($file)函数可以输出文件的时间戳,而不能使用time()函数来进行缓存,因为后者的值每秒钟都会变化。 - neoteknic
小修改可能不会改变大小,我使用hash_file('md5', 'js/main.js') - Fakhamatia

36

这种用法已经被弃用了: https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache

这个答案虽然晚了6年,但我没有在很多地方看到这个答案... HTML5引入了应用程序缓存,用于解决这个问题。我发现我正在编写的新服务器代码会导致保存在人们浏览器中的旧javascript崩溃,因此我想找到一种方法来过期他们的javascript。使用一个类似这样的清单文件:

CACHE MANIFEST
# Aug 14, 2014
/mycode.js

NETWORK:
*
每次您希望用户更新其缓存时,都需要使用新的时间戳生成此文件。值得注意的是,如果添加了此内容,则浏览器将在清单告诉它之前不会重新加载(即使用户刷新页面)。

这个解决方案非常好,只要记得更新清单文件就行了 :) - Mattis
34
请阅读文档,因为此功能已从Web标准中删除。 https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache - Flavia Obreja
1
顺便说一下,我最终没用这个解决方案。使用/维护 ?<version> 方法要简单得多。 - amos
1
现在它已经过时了! - Amin.Qarabaqi

21
不是所有的浏览器都会缓存带有“?”的文件。为了尽可能确保文件被缓存,我在文件名中包含了版本号。 所以,我使用了stuff_123.js代替stuff.js?123。 我在Apache中使用了mod_redirect(我想是这个)来将stuff_*.js重定向到stuff.js

1
请问您能详细说明一下您在使用mod_redirect时在.htaccess文件中所做的操作吗? - Venkat D.
3
这种方法的详细说明可以在http://particletree.com/notebook/automatically-version-your-css-and-javascript-files/找到。 - Karl Bartel
3
为了方便将来参考,如果您能在回答中包含您的.htaccess代码那就太好了。 - Fizzix
3
不缓存带有“?”的文件的浏览器有哪些? - rosell.dk

14

现今常见的做法是在文件名中生成内容哈希码,以强制浏览器(特别是IE)重新加载JavaScript文件或CSS文件。

例如:

vendor.a7561fb0e9a071baadb9.js
main.b746e3eb72875af2caa9.js

这通常是由构建工具(例如Webpack)完成的。如果有人正在使用Webpack并想尝试,请参考此处了解更多细节。


12

对于ASP.NET页面,我正在使用以下内容

BEFORE

<script src="/Scripts/pages/common.js" type="text/javascript"></script>

AFTER (强制重新加载)

<script src="/Scripts/pages/common.js?ver<%=DateTime.Now.Ticks.ToString()%>" type="text/javascript"></script>

添加DateTime.Now.Ticks非常有效。


36
这个做法违反了客户端所有的缓存机制。dummy参数应该被替换成类似于"{主版本号}{次版本号}{构建编号}_{修订号}"这样的内容,每次发布都应该是唯一的。 - Tohid
18
虽然在开发环境中这可能是一个好的解决方案,但它不适合生产环境。每次加载页面时,它都会完全禁用缓存该文件。假设一天有10k个页面加载,其中包含一个50Kb的文件,则代表每天会产生500Mb的Javascript文件。 - PhilDulac
@PhilDulac 你可以将它从Ticks更改为返回例如日期、月份或者每月的星期几的字符串值。最终,它只是展示了如何使用?v方法。 - alex
3
@alex 确实。我只是想警告一下,如果回答中展示的用法投入生产,可能会对开发中没有显示的影响产生影响。 - PhilDulac
2
确保每天加载新副本的一种可能方法是使用 '<script src="/Scripts/pages/common.js?ver<%=DateTime.Now.ToString("yyyyMMdd")%>" type="text/javascript"></script>'。因此,它在一天的开始时只加载一次,然后缓存。 - Robb Sadler

8

针对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.asax中,Global.JsPostfix和Global.CssPostfix是通过以下方式计算的:
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();
    }
    ...
}

我正在使用.Ticks(请参见我在此页面上的答案) - Ravi Ram

6
我们一直在为用户创建一个SaaS,并提供一个脚本来附加到他们的网页中。由于用户将脚本附加到他们的网站上用于功能,因此无法将版本与脚本一起附加,我也不能强制要求他们每次更新脚本时更改版本。 所以,我们找到了一种方法,在用户调用原始脚本时加载更新版的脚本。 提供给用户的脚本链接
<script src="https://thesaasdomain.com/somejsfile.js" data-ut="user_token"></script>
if($('script[src^="https://thesaasdomain.com/somejsfile.js?"]').length !== 0) {
   init();
} else {
   loadScript("https://thesaasdomain.com/somejsfile.js?" + guid());
}

var loadscript = function(scriptURL) {
   var head = document.getElementsByTagName('head')[0];
   var script = document.createElement('script');
   script.type = 'text/javascript';
   script.src = scriptURL;
   head.appendChild(script);
}

var guid = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

var init = function() {
    // our main code
}

解释:

用户已在其网站上附加了所提供的脚本,我们使用jQuery选择器检查所附带的唯一标记是否存在,如果不存在,则使用较新的令牌(或版本)动态加载它。

这样做会导致同一个脚本被调用两次,可能会影响性能,但它确实解决了强制脚本不从缓存中加载而不将版本放入实际脚本链接给用户或客户端的问题。

免责声明:如果性能对您很重要,请勿使用此方法。


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