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

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

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

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


我发现da5id的18921">约翰·米利的和da5id的的建议很有用。事实证明有一个术语:版本自动化

我在下面发布了一个新的答案,它结合了我最初的解决方案和约翰的建议。

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

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

537868 次浏览

此解决方案是用PHP编写的,但它应该很容易适应其他语言。

原始的.htaccess正则表达式可能会导致像json-1.3.js这样的文件出现问题。解决方案是仅在末尾正好有10位数字时重写。(因为10位数字涵盖了从9/9/2001到11/20/2286的所有时间戳。)

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

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

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

/***  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,都可以从以下位置更改它:

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

对此:

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

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

这也可以与图像、图标和JavaScript一起使用。基本上是任何不是动态生成的东西。

您可以将?foo=1234放在CSS/JavaScript导入的末尾,将1234更改为您喜欢的任何内容。

这里的想法是?参数无论如何都会在请求上被丢弃/忽略,并且您可以在推出新版本时更改该数字。


备注:关于这究竟如何影响缓存存在一些争论。我相信它的一般要点是get请求,带或不带参数应该都是可缓存的,因此上述解决方案应该有效。

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

我听说过这叫做“自动版本控制”。最常见的方法是在URL的某个地方包含静态文件的修改时间,并使用重写处理程序或URL配置将其删除:

另见:

如果您将会话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>

简单的客户端技术

一般来说,缓存是好的……所以有几种技术,这取决于你是在开发网站时自己解决问题,还是在正式生产环境中控制缓存。

您网站的普通访问者不会有与您在开发网站时相同的体验。由于普通访问者访问网站的频率较低(可能每月只有几次,除非您是Google或hi5 Networks),那么他们不太可能缓存您的文件,这可能就足够了。

如果您想在浏览器中强制执行新版本,您可以随时向请求添加查询字符串,并在进行重大更改时增加版本号:

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

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

另一方面,如果您正在开发一个网站,您不希望每次保存对开发版本的更改时都更改版本号。那将是乏味的。

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

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

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

同样值得注意的是,浏览器并不一定吝啬于将文件保存在缓存中。浏览器对此有策略,它们通常遵守HTTP规范中制定的规则。当浏览器向服务器发出请求时,响应的一部分是到期标头……一个日期,告诉浏览器应该在缓存中保存多长时间。下次浏览器遇到对同一文件的请求时,它会看到缓存中有一个副本,并查看到期日期以决定是否应该使用它。

所以信不信由你,实际上是您的服务器使浏览器缓存如此持久。您可以调整您的服务器设置并更改到期标头,但我上面写的小技巧可能是您进行此操作的简单得多的方法。由于缓存很好,您通常希望将该日期设置为遥远的未来(“远未来过期标头”),并使用上述技术强制更改。

如果您对有关HTTP或如何发出这些请求的更多信息感兴趣,一本好书是Steve Souders的“高性能网站”。这是对该主题的非常好的介绍。

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

正如您所注意到的,“no-ache”的HTTP缓存控制标头并不总是有效。HTTP 1.1规范允许用户代理决定是否请求新副本。(如果您只看指令的名称,这是不直观的。去阅读实际的HTTP 1.1缓存规范……它在上下文中更有意义。)

简而言之,如果你想要铁一样的缓存控制

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

在您的响应标题中。

不要使用foo.css?version=1

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

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

所以你的URL应该是这样的

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

您仍然可以使用重写规则去掉哈希,但优点是现在您可以将缓存策略设置为“永久缓存”,因为如果URL相同,这意味着文件不变。

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

只需在每次CSS更改时运行该脚本即可。浏览器只会在更改文件时重新加载文件。如果您进行编辑然后撤消它,则无需费力确定需要返回哪个版本,以便访问者不会重新下载。

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

def import_tag(pattern, name, **kw):if name[0] == "/":name = name[1:]# Additional HTML attributesattrs = ' '.join(['%s="%s"' % item for item in kw.items()])try:# Get the files modification timemtime = os.stat(os.path.join('/documentroot', name)).st_mtimeinclude = "%s?%d" % (name, mtime)# This is the same as sprintf(pattern, attrs, include) in other# languagesreturn 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">

当然,优点是您不必再次更改超文本标记语言内容,触摸CSS文件将自动触发缓存失效。它工作得很好,开销不明显。

假设您有一个可用的文件:

/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

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

我已经用Javaservlet实现了一个缓存解决方案,并使用委托给底层资源(即/styles/screen.css)的servlet简单地处理对/v/*的请求。在开发模式下,我设置了缓存标头,告诉客户端始终检查服务器资源的新鲜度(如果你委托给Tomcat的DefaultServlet,而.css.js等文件没有改变,这通常会导致304),而在部署模式下,我设置了标头,上面写着“永远缓存”。

我建议执行以下过程:

  • 在部署时为CSS和JavaScript文件版本。类似:screen.1233.css(如果您使用版本控制系统,数字可以是您的SVN版本)

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

如果您使用的是jQuery,则有一个名为缓存的选项将附加一个随机数。

我知道这不是一个完整的答案,但它可能会节省你一些时间。

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

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

css-element.txt的内容

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

所以你想链接到my-new-css.css或其他什么的那一天,你只需更改包含。

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

这将导致以下结果:

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

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

重写规则需要对末尾包含点符号版本控制的JavaScript或CSS文件进行小更新。例如,json-1.3.js

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

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

感谢Kip的完美解决方案

我将其扩展为将其用作Zend_view_Helper。因为我的客户端在虚拟主机上运行他的页面,我也为此扩展了它。

/*** 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 fileif (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 onereturn preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);} else {return $filePath;}}}}

谷歌apachemod_pagespeed插件将为您执行自动版本控制。它真的很光滑。

它在退出网络服务器时解析超文本标记语言(适用于PHP,ruby on rails,Python,静态超文本标记语言-任何东西)并重写指向CSS,JavaScript,图像文件的链接,因此它们包含id代码。它在修改后的URL处提供文件,并对其进行很长的缓存控制。当文件更改时,它会自动更改URL,因此浏览器必须重新获取它们。它基本上可以正常工作,无需对您的代码进行任何更改。它甚至会在出去的时候缩小您的代码。

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

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

如果您使用ASP.NETMVC,那么您可以查看代码在我的另一个答案里

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

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

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

托马斯的回答是正确的。

使用“querystring”方法将不会被缓存,如下面的Steve Souders所引用:

…一个流行的代理,不缓存资源查询字符串。

Toma的建议使用style.TIMESTAMP.css是好的,但是MD5会更好,因为只有当内容真正改变时,MD5也会改变。

我发现在资源URL中使用基于时间戳或哈希的区分器的方法存在问题,该方法在服务器请求时被剥离。包含指向例如样式表可能也会被缓存的链接的页面。因此,缓存页面可能会请求旧版本的样式表,但它将提供最新版本,这可能适用于请求页面,也可能不适用。

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

我不知道为什么你们这些家伙/女孩要付出这么多的痛苦来实施这个解决方案。

如果获取文件的修改时间戳并将其作为查询字符串附加到文件中,您需要做的就是。

在PHP中,我会这样做:

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

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

SCdF提出的另一个想法是在文件中附加一个虚假的查询字符串。(pi提交了一些自动使用时间戳作为虚假查询字符串的Python代码。)然而,关于浏览器是否会缓存带有查询字符串的文件有一些讨论。(记住,我们希望浏览器缓存文件并在未来的访问中使用它。我们只希望它在文件发生变化时再次获取文件。)由于不清楚虚假查询字符串会发生什么,我不接受这个答案。

<link rel="stylesheet" href="file.css?<?=hash_hmac('sha1', session_id(), md5_file("file.css")); ?>" />

对文件进行哈希处理意味着当它发生变化时,查询字符串将发生变化。如果没有,它将保持不变。每个会话也强制重新加载。

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

对于JavaServlet环境,您可以查看jawr库。功能页面解释了它如何处理缓存:

Jawr将尽最大努力强制您的客户端缓存资源。如果浏览器询问文件是否更改,则会发回304(未修改)标头,没有内容。另一方面,使用Jawr,您将100%确定所有客户端都下载了您的包的新版本。您资源的每个URL都将包含一个自动生成的基于内容的前缀,每当资源更新时该前缀会自动更改。一旦您部署新版本,包的URL也会更改,因此客户端不可能使用旧的缓存版本。

该库还进行JavaScript和CSS缩小,但如果您不想要,可以将其关闭。

您可以简单地使用CSS和JavaScript URL添加一些随机数,例如

example.css?randomNo = Math.random()

对于ASP.NET我提出以下具有高级选项的解决方案(调试/发布模式,版本):

以这种方式包含JavaScript或CSS文件:

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

全球范围. jsPostFix全球范围. csspostGlobal.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 !DEBUGJsPostfix += ".min";#endifJsPostfix += ".js?" + jsVersion + "_" + buildNumber;if (updateEveryAppStart){Random rand = new Random();JsPosfix += "_" + rand.Next();}...}

JavaScript文件的另一种方法是将jQuery$.getScript$.ajaxSetup选项cache: false结合使用。

而不是:

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

您可以使用:

$.ajaxSetup({cache: false});
$.getScript('scripts/app.js'); // GET scripts/app.js?_1391722802668

另一个建议ASP.NET网站,

  1. 为不同的静态文件设置不同的cache-control:max-age值。

  2. 对于CSS和JavaScript文件,在服务器上修改这些文件的可能性很高,因此设置1或2分钟或满足您需要的最小缓存控制:max-age值。

  3. 对于图像,将远日期设置为缓存控制:max-age值,例如360天。

  4. 通过这样做,当我们发出第一个请求时,所有静态内容都会以200-OK响应下载到客户端机器。

  5. 在随后的请求中,两分钟后,我们看到对CSS和JavaScript文件的304-未修改请求,这避免了我们对CSS和JavaScript版本的控制。

  6. 不会请求图像文件,因为它们将从缓存内存中使用,直到缓存过期。

  7. 通过使用下面的web.config配置,我们可以实现上述行为,

    <system.webServer><modules runAllManagedModulesForAllRequests="true"/><staticContent><clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="00.00:01:00"/></staticContent><httpProtocol><customHeaders><add name="ETAG" value=""/></customHeaders></httpProtocol></system.webServer>
    <location path="Images"><system.webServer><staticContent><clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="180.00:00:00" /></staticContent></system.webServer></location>

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

有关详细信息,请参阅:使用应用程序缓存

对于ASP.NET4.5或更高,您可以使用脚本捆绑

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

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

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

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

例如,这里有一个简单的bash脚本,它从标准输入读取文件名列表并将包含哈希的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; doecho "$delim\"$l\": \"`md5 -q $l`\""delim=","doneecho "}"

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

这是一个纯JavaScript解决方案

(function(){
// Match this timestamp with the release of your codevar 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)强制从服务器刷新页面。

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

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

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

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

?$Now.Format(dmYHis)

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

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

https://superuser.com/a/512833

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

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

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

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

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

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

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

  • 资源在初始页面加载后一段时间异步请求
  • 应用程序逻辑假设有关资源内容的内容(在未来版本中可能会更改)

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

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

最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个URL调用URL生成器。或者您可以巧妙地使用#0标签并在一个地方更改当前版本。

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

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

最后,我写了一个快速页面(包括所有代码),作为发布过程的一部分,自动版本一个超文本标记语言/JavaScriptindex.html文件。它工作完美,只根据上次修改的日期更新新文件。

你可以在自动更新您的SPAindex.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");}

我没有找到动态创建脚本节点(或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>

似乎这里的所有答案都建议在命名方案中进行某种版本控制,这有其缺点。

浏览器应该通过读取Web服务器的响应,特别是HTTP标头来清楚地知道要缓存什么,不要缓存什么-这个资源的有效期是多久?自从我上次检索到这个资源以来,这个资源是否更新了?等等。

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

关于它是如何工作的更深入的解释在Web缓存的工作原理中。

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

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

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

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

如果您使用的是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;

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

示例超文本标记语言输出(filemtime返回时间为unix时间戳

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

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

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

.load中执行此操作,因此它不会像循环一样刷新。

GoogleChrome有硬重装空缓存和硬重载选项。您可以单击并按住重新加载按钮(在检查模式中)选择一个。

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

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

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

从现有答案的小改进…

使用随机数或会话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并滚动到设置偏好网络→*禁用缓存(当开发工具打开时)

ChromeDevTools

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

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

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

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

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

<script src="https://jessietessie.github.io/google-translate-token-generator/google_translate_token_generator.js" integrity="sha384-muTMBCWlaLhgTXLmflAEQVaaGwxYe1DYIf2fGdRkaAQeb4Usma/kqRWFWErr2BSi" crossorigin="anonymous"></script>

对于开发:使用浏览器设置:例如,Chromenetwork 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>

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

暂时删除缓存。

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

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

大多数小项目只是玩玩而已——它们永远不会在生产中结束。所以对他们来说,你不需要更多的东西。

因此,如果您使用ChromeDevTools,您可以遵循这种禁用缓存方法,如下图所示:

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

如果您有Firefox缓存问题:

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

如何在开发过程中禁用Firefox中的缓存

您还需要一种机制来强制重新加载以用于生产,因为如果您经常更新应用程序并且您没有提供像上面答案中描述的专用缓存同步机制,您的用户将使用旧的缓存无效模块。

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

我们有一个具有不同实现方式的解决方案。我们使用上面的解决方案。

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也会更改,因此文件的版本会更改,并且在下一次打开浏览器时,它会检测到一个新文件并获取它。

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

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

这是我的基于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.htmlecho "$current_timestamp" >timestamp.txt

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

我已经解决了这个问题ETag

#请求参数

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

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

在ASP.NETCore中,您可以通过添加'asp-append-version'来实现这一点:

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

它将生成超文本标记语言:

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

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

如果你不希望客户端缓存文件,这个解决方案似乎是最快实现的。如果你在footer.php加载文件,请用time()调整部分:

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

或者使用检查器中的“网络”([CTRL]+[I]),单击“禁用缓存”,单击垃圾图标,单击“加载”/“获取”