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


993

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

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

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

更新:

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

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

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

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


我的.htaccess文件中有此文件,缓存文件从没有任何问题:ExpiresActive On ExpiresDefault "modification"
Frank Conijn 2014年

2
我绝对同意,到目前为止,将版本控制信息添加到文件的URL是最好的方法。它始终对所有人有效。但是,如果您不使用它,而只是偶尔需要在您自己的浏览器中重新加载一个CSS或JS文件,只需在其自己的标签中打开它,然后按SHIFT-reload(或CTRL-F5)!您可以使用JS有效地完成相同的操作,方法是将文件加载到(隐藏的)iframe中,等待文件加载,然后调用iframe.contentWindow.location.reload(true)。请参阅stackoverflow.com/a/22429796/999120的方法(4)-这是关于图像的,但同样适用。
Doin 2015年

2
我真的很感谢这个问题的提出方式,并从那时起得到了更新。它完全描述了我在答案中应该期待的内容。从现在开始,我将在我的问题中遵循这种方法。干杯!
RD22

Answers:


455

更新: 重写以合并John Millikinda5id的建议。该解决方案是用PHP编写的,但应易于适应其他语言。

更新2:结合尼克·约翰逊Nick Johnson)的评论,即原始.htaccess正则表达式可能会导致文件问题json-1.3.js。解决方案是仅在末尾恰好有10位数字时才重写。(因为10位数字涵盖了从9/9/2001到11/20/2286的所有时间戳。)

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

RewriteEngine on
RewriteRule ^(.*)\.[\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" />

这样,您无需再次修改link标记,并且用户将始终看到最新的CSS。浏览器将能够缓存CSS文件,但是当您对CSS进行任何更改时,浏览器会将其视为新的URL,因此它将不使用缓存的副本。

这也可以用于图像,图标和JavaScript。基本上不是动态生成的任何内容。


16
我自己的静态内容服务器的功能完全相同,除了我使用参数进行版本控制(base.css?v = 1221534296)而不是更改文件名(base.1221534296.css)。我怀疑您的方式可能会更有效率。很酷。
詹斯·罗兰

4
@Kip:非常光滑的解决方案。显然,URL重写不仅可以提供漂亮的URL,还可以提供更多的功能。
James P.

37
我看到了一个问题,它多次访问文件系统-确切地-链接数*请求数/秒...这可能对您来说可能不是问题。
托马什Fejfar

3
@AlixAxel:否,当参数更改时,浏览器会重新获取它,但是某些公共代理不会缓存带有url参数的文件,因此最佳实践是在路径中包含该版本。与WPO中的所有其他性能瓶颈相比,mod_rewrite开销微不足道-Jens
Roland

8
第一次file_exists检查真的必要吗?filemtime会在失败时返回false,所以为什么不只是将filemtime值分配给变量并在重命名文件之前检查它是否为false?这将减少一项不必要的文件操作,而这实际上会加起来。
加文2014年

184

简单的客户端技术

通常,缓存是好的。.因此,有两种技术,取决于您是在开发网站时自行解决问题还是在生产环境中控制缓存。

网站的一般访问者不会拥有与开发网站时相同的体验。由于普通访问者访问该网站的频率较低(除非您是Google或hi5网络,否则每月访问几次),因此他们将文件缓存的可能性较小,这足够了。如果要向浏览器强制使用新版本,则始终可以向请求添加查询字符串,并在进行重大更改时提高版本号:

<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规范中规定的规则进行操作。当浏览器向服务器发出请求时,响应的一部分是EXPIRES标头。日期告诉浏览器应将其保留在缓存中多长时间。下次浏览器遇到对同一个文件的请求时,它将看到它在缓存中有一个副本,并期待EXPIRES日期决定是否应使用它。

因此,不管您相信与否,实际上是您的服务器使浏览器缓存如此持久。您可以调整服务器设置并更改EXPIRES标头,但是我上面编写的小技巧可能是解决问题的一种简单得多的方法。由于缓存是好的,所以您通常希望将日期设置为较远的将来(“ Far-future Expires Header”),并使用上述技术来强制进行更改。

如果您对HTTP的更多信息或如何发出这些请求感兴趣,那么一本好书就是Steve Souders的“ High Performance Web Sites”。这是对该主题的很好的介绍。


3
在活动开发期间,使用Javascript生成查询字符串的快速技巧非常有用。我用PHP做过同样的事情。
艾伦·图灵

2
这是达到原始海报预期效果的最简单方法。如果您希望每次加载页面时都强制重新加载.css或.js文件,则mod_rewrite方法效果很好。此方法仍然允许缓存,直到您实际更改文件并真正希望它强制重新加载为止。
scott80109 2014年

@keparo,如果要手动更改此页面,则所有页面中的jquery数量都足够,这需要一个月的时间。如果您可以帮助我解决所有问题而无需编码每个页面。
饼干2014年

1
当我使用CSS时,这似乎不适用于我的CSS:<link href='myCss.css?dev=14141'...>
Noumenon 2015年

3
这不是可行的解决方案。大量的浏览器只会拒绝缓存带有查询字符串的任何内容。这就是如果您对静态内容的引用具有查询字符串时,Google,GTMetrix和类似工具会发出标记的原因。虽然这肯定是一个不错的开发解决方案,但绝对不是生产解决方案。另外,浏览器控制缓存,而不是服务器。服务器只是建议何时刷新。浏览器不必收听服务器(通常不收听)。移动设备就是一个很好的例子。
Nate I

113

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

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


1
很好,但仍处于测试阶段。可以用于企业服务吗?
Sanghyun Lee 2011年

26
当它显然是浏览器发出的消息时,这是错误的(自动修改源代码)。给我们(开发人员)一个真正的擦脑刷新:<ctrl> + F5
T4NK3R 2011年

25
mod_pagespeed在功能上等同于html / css / js的全自动构建/编译步骤。我认为您很难找到认真的开发人员,他们认为构建系统本质上是错误的,或者完全自动化是有问题的。干净构建的类比是清除mod_pagespeed的缓存:code.google.com/p/modpagespeed/wiki/…
Leopd

3
@ T4NK3R mod_pagespeed不必对您的源代码做任何事情来进行缓存管理,只是提到它可以帮助实现缩小等功能。至于是否“错误”,那完全是主观的。这对您来说可能是错的,但这并不意味着它在本质上是不好的
Madbreaks'Aug 3'3

2
它也可以与nginx一起使用,尽管您必须从以下来源进行构建:developers.google.com/speed/pagespeed/module/…–
Rohit

93

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

因此,您的网址将类似于

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

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

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

每次CSS更改时,只需运行该脚本即可,一切都很好。更改后,浏览器将仅重新加载文件。如果您进行了修改然后撤消了修改,那么就可以轻松确定要返回到哪个版本,以免访问者重新下载。


1
不幸的是,我不知道如何实现它。请咨询...更多详细信息...
Michael Phelps 2014年

用shell,ruby等实现非常棒
Peter

3
很好的解决方案..但是我认为在每个页面访问中,在每个文件请求(css,js,images,html..etc)中计算文件的哈希值都会浪费资源。
DeepBlue

对于那些使用gulp,grunt或webpack进行js或css捆绑的用户来说,这是一个标准解决方案,每种解决方案的实现方式都不同,但是将文件作为构建步骤进行哈希处理是常见的,并建议用于现代捆绑应用程序
BrandonSørenCulley

@DeepBlue-回答说“每次CSS更改都运行该脚本”。并非每次访问页面时都如此。OTOH答案遗漏了主要细节-更改后的哈希如何成为URL的一部分?我不知道...
ToolmakerSteve19年

70

不知道为什么你们为实施该解决方案付出了如此多的痛苦。

获取文件的修改后的时间戳并将其作为查询字符串附加到文件后,您需要做的所有事情

在PHP中,我会这样做:

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

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


您可以使用mycss.css?1234567890
加文2014年

3
非常优雅,尽管我对其进行了少许修改<link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>,以防万一此线程上有关使用GET变量缓存URL(使用建议格式)的参数是正确的
luke_mclachlan 2015年

在我最后的评论中,我已经看到了wordpress的用法?ver=,谁知道呢!
luke_mclachlan 2015年

很好的解决方案。另外,对我来说,我发现filemtime不适用于完全限定的域名(FQDN),因此我将FQDN用于href部分,将$ _SERVER [“ DOCUMENT_ROOT”]用于filemtime部分。例如:<link rel =“ stylesheet” href =“ http://theurl/mycss.css?v = <?php echo filemtime($ _ SERVER [” DOCUMENT_ROOT“]。'/mycss.css')?>” />
rrtx2000 '16

十分感谢。简单又好。这是在Python中:progpath = os.path.dirname(sys.argv [0])def versionize(file):timestamp = os.path.getmtime('%s /../ web /%s'%(progpath ,file))返回'%s?v =%s'%(文件,时间戳)print <link href =“%s” rel =“ stylesheet”''type =“ text / css” />'\%versionize( 'css / main.css')
dlink

52

您可以只将?foo=1234css / js导入的末尾放在1234上,将其更改为您喜欢的任何形式。看看SO html源代码的示例。

有那个想法吗?无论如何,参数都会被丢弃/忽略,并且在您推出新版本时可以更改该数字。


注意:关于它如何影响缓存,存在一些争论。我相信一般的主旨是,带有或不带有参数的GET请求都应该是可缓存的,因此上述解决方案应该可以工作。

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


废话。查询字符串(又称GET参数)是URL的一部分。它们可以并且将被缓存。这是一个很好的解决方案。
troelskn

9
@troelskn:HTTP 1.1规范另有说明(关于带有查询参数的GET和HEAD请求):除非服务器提供明确的到期时间,否则缓存不得将对此类URI的响应视为最新。参见w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
Michael Johnson,

4
我在所有主流浏览器中都尝试了版本的查询字符串类型,并且它们确实缓存了文件,无论是否规范。但是,我认为最好使用style.TIMESTAMP.css格式而又不滥用查询字符串,因为缓存代理软件仍然有可能不会缓存文件。
Tomas Andrle,2009年

34
出于任何原因,值得注意的是,Stackoverflow本身使用查询字符串方法。
杰森2010年

2
验证使用?= parameter不会使浏览器在参数更改时重新获取缓存的文件。唯一的方法是按照Kip的回答,在服务器端以编程方式更改文件名本身
arunskrish 2013年


27

现有的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>标签等以指向版本号目录之一中的该文件

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

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


迈克尔-您的评论非常相关。我在这里恰好试图为我的SPA找到解决方案。我有一些建议,但必须自己提出解决方案。最后,我对自己的想法感到非常满意,因此写了一篇博客文章并回答了这个问题(包括代码)。感谢您的指点
斯塔特勒

很棒的评论。当人们一直谈论将缓存清除和HTTP缓存作为网站缓存问题的真正解决方案而又不注意SPA的新问题时,我无法理解,就好像这只是一个边缘情况。
David Casillas

1
出色的响应和绝对理想的策略!还有提及base标签的奖励积分!至于支持旧代码:这并非总是可能,也不总是一个好主意。新版本的代码可能支持对应用程序其他部分进行重大更改,或者可能涉及紧急修复程序,漏洞修补程序等。我尚未亲自实施此策略,但我一直觉得总体体系结构应允许部署将旧版本标记为,obsolete并在下次进行异步调用时强制重新加载(或仅通过WebSocket强制取消对所有会话的身份验证) )。
强尼·阿斯玛

很高兴看到有关单页应用程序的深思熟虑的答案。
Nate I

如果您想搜索更多信息,那就是“蓝绿色部署”。
费尔

15

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


1
首先,浏览器不缓存,这就是HTTP的功能。http为什么会关心URI的结构?是否有对规范的正式引用,该规范指出HTTP缓存应了解URI的语义,以便它不会缓存带有查询字符串的项目?
AnthonyWJones

13
包含缓存对象功能(检查浏览器的缓存目录)的Web浏览器。HTTP是一种协议,包括从服务器到客户端(代理,浏览器,蜘蛛等)的指令,这些指令建议进行缓存控制。
tzot

13

在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输出示例(filemtime返回时间,以Unix时间戳记

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

此命令在html中的输出是什么?还有,如果我只需要续订?v = 3,?v = 4等版本,该怎么办?-用户每次进入网站时都不会强制浏览器加载CSS
Gediminas

filemtime:“此函数返回写入文件的数据块的时间,即文件内容被更改的时间。” src:php.net/manual/en/function.filemtime.php
KamilKiełczewski17年

11

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

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

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

2
感谢您的输入!自从我写了这篇文章以来,我也为此而感到困惑。我的解决方案是仅在文件名的最后部分恰好包含十位数字时重写。(10位数字涵盖了从9/9/2001到11/20/2286的所有时间戳。)我已经更新了答案,以包括此正则表达式:^(.*)\.[\d]{10}\.(css|js)$ $1.$2
Kip 2010年

我了解正则表达式,但不了解您在此解决的问题[^.]。同样,在\d字符类内部编写没有任何好处- \d+会做同样的事情。发布后,您的模式将匹配任意数量的字符(贪婪地匹配),然后是文字点,然后是非点,然后是一个或多个数字,然后是点,然后是cssjs,然后是文件名的末尾。样本输入不匹配:regex101.com/r/RPGC62/1
mickmackusa,

10

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

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

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


请帮我,我没有对bundle.config进行任何更改,只是更改了CSS或js文件,那么我该如何解决缓存问题?
vedankita kumbhar

10

这是一个纯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)强制从服务器刷新页面。

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

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


我尝试过类似的操作,此操作仅在重新加载的页面上有效,但是如果该站点具有共享相同CSS /图像的多个页面,则其他页面仍将使用旧资源。
深蓝

9

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

这将导致以下结果:

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

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


8

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

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

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

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


而且,如果您使用Chrome作为开发环境,则另一种非侵入式解决方案是禁用缓存:在“设置”齿轮下,您可以通过选择“禁用缓存”来使磁盘缓存无效(注意:DevTools必须可见/打开)为这个工作)。
Velojet

7

感谢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 file
            if (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 one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {

                return $filePath;
            }
        }
    }

}

干杯,谢谢。


7

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

6

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


为了澄清,通过“检查模式”,它们指的是“开发工具”又名F12,又名CTRL + SHIFT + 1,又名ant menu> More Tools> Developer Tools,又名right click> Inspect Element。在开发工具的某个位置(我忘记了位置)中也隐藏了一个设置,可以在每次重载时进行硬重载。
乔尼·阿斯玛

5

如果将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>

5

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

/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');
}

其网址实际上是:

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

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

我已经使用Java servlet的这种技术实现了一个缓存解决方案,并且仅使用/v/*委派给基础资源(即/styles/screen.css)的servlet 处理请求。在开发模式我一系列缓存头,告诉客户要经常检查资源的新鲜感与服务器(这通常导致304,如果你委托给Tomcat的DefaultServlet.css.js等文件并没有改变),而在部署模式我设置标题为“永远缓存”。


如果仅使用相对URL,则只需添加一个可以在必要时重命名的文件夹即可。然后,确保从基本文件夹重定向到正确的文件夹,即在PHP:中<?php header( 'Location: folder1/login.phtml' ); ?>
Gruber 2012年

1
使用第二种方法,对CSS的更改将使用相对URL引用的所有图像的缓存副本无效,这可能是不希望的,也可能是不希望的。
TomG 2013年

5

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

example.css?randomNo=Math.random()

5

对于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();
    }
    ...
}

4

我最近使用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 type="text/javascript" """ +\
        """ %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()会造成瓶颈吗?
hoju 2012年

如果磁盘非常慢且请求很多,则@Richard stat可能是瓶颈。在这种情况下,您可以将时间戳缓存在内存中的某个位置,并在每次新部署时清除此缓存。但是,在大多数用例中,这种复杂性将不是必需的。
pi。

4

如果您使用的是git + PHP,则每次git repo发生更改时,都可以使用以下代码从缓存中重新加载脚本:

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

4

如果您是希望避免缓存的开发人员,则chrome网络选项卡具有禁用缓存选项。否则,您可以在没有使用两个脚本标签的服务器呈现框架的情况下完成此操作。

<script type="text/javascript">
    document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
    // can't use myfile.js stuff yet
</script>')
<script type="text/javascript">
    // do something with myfile.js
</script>

3

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

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

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

https://www.mnot.net/cache_docs/#WORK对此工作原理进行了更深入的说明


3

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

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

在Chrome上不起作用。仍从磁盘缓存中加载资产
Jason Kim

3

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

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

3

这个问题太老了,当有人用谷歌搜索这个问题时,它会首先出现。这不是对op所希望的问题的答案,而是对开发和测试中有此问题的开发人员的回答。而且我不能对此主题发布新的问题,因为它将被标记为重复。

像许多其他人一样,我只是想简短地删除缓存。

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

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

大多数小型项目只是在玩耍-他们从未最终投入生产。因此对于他们来说,您不再需要其他任何东西。

这样,如果您使用Chrome Dev Tools,则可以遵循以下禁用缓存的方法,如下图所示: 如何强制Chrome重新加载缓存的文件

如果您有firefox缓存问题: 如何强制在Firefox上重新加载资产

在开发过程中如何禁用Firefox中的缓存 仅在开发中执行此操作,您还需要一种机制来强制重新加载以进行生产,因为如果您频繁更新您的应用,并且您没有提供专用的缓存同步机制(如答案中所述),则用户将使用旧的缓存无效模块。以上。

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

希望这个答案很明确,现在您不需要。


OP提出了一些问题,然后回答了其他问题。这不是力载荷的地方,但在生产中,你不能要求用户遵循上面禁用缓存等
Jitendra Pancholi

2

我建议执行以下过程:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.