当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何强制浏览器重新加载缓存的CSS/JS文件?》经验,为你挑选了22个好方法。

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

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

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

更新:

在这里讨论了一段时间后,我发现John Millikinda5id的建议很有用.事实证明,有一个术语:自动版本控制.

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

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

由于不清楚伪造的查询字符串会发生什么,我不接受这个答案.



1> Kip..:

更新: 重写以纳入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,请从以下位置进行更改:


对此:


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

这也适用于图像,favicon和JavaScript.基本上任何不动态生成的东西.


我发现这有一个问题,它多次访问文件系统 - 确切地说 - 链接数量*请求数/秒......这对您来说可能是也可能不是问题.
我自己的静态内容服务器完全相同,除了我使用参数进行版本控制(base.css?v = 1221534296)而不是文件名更改(base.1221534296.css).我怀疑你的方式可能会更有效率.很酷.
第一个`file_exists`检查真的有必要吗?`filemtime`将在失败时返回false,那么为什么不将filemtime值分配给变量并在重命名文件之前检查它是否为false?这将减少一个不必要的文件操作,这将真正加起来.
@Kip:非常灵活的解决方案.URL改写显然比提供更好的网址提供更多功能.
@AlixAxel:不,浏览器会在参数更改时重新获取它,但是某些公共代理不会使用url参数缓存文件,因此最佳做法是在路径中包含该版本.与WPO中的每个其他性能瓶颈相比,mod_rewrite开销微乎其微
我将函数修剪为:`$ filetime = strpos($ file,'/')=== 0?filemtime($ _ SERVER ['DOCUMENT_ROOT'].$ file):false; if($ filetime)返回preg_replace('{\\.([^./] +)$}',".$ filetime).\ $ 1",$ file); 否则返回$ file;`

2> keparo..:

简单的客户端技术

一般来说,缓存很好..所以有几种技术,取决于你在开发网站时是否自己解决问题,或者你是否试图在生产环境中控制缓存.

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


这将确保每个人都获得新文件.它的工作原理是浏览器查看文件的URL以确定它是否在缓存中有副本.如果您的服务器未设置为对查询字符串执行任何操作,则将忽略该服务器,但该名称将看起来像浏览器的新文件.

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

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



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

值得注意的是,浏览器并不一定吝啬将文件保存在缓存中.浏览器有针对此类事情的策略,它们通常按照HTTP规范中规定的规则进行播放.当浏览器向服务器发出请求时,响应的一部分是EXPIRES标头..一个告诉浏览器应该在缓存中保留多长时间的日期.下次浏览器遇到对同一文件的请求时,它会看到它在缓存中有一个副本,并查看EXPIRES日期以决定是否应该使用它.

不管你信不信,它实际上是你的服务器使浏览器缓存如此持久.您可以调整服务器设置并更改EXPIRES标头,但我上面写的小技巧可能是一种更简单的方法.由于缓存很好,您通常希望将该日期设置为远期("Far-future Expires Header"),并使用上述技术强制进行更改.

如果您对有关HTTP的更多信息或如何提出这些请求感兴趣,那么一本好书就是Steve Souders的"高性能网站".这是对这个主题的一个非常好的介绍.


使用Javascript生成查询字符串的快速技巧在活动开发期间非常有效.我用PHP做了同样的事情.
这是完成原始海报所需结果的最简单方法.如果要在加载页面时强制重新加载.css或.js文件,则mod_rewrite方法很有效.此方法仍允许缓存,直到您实际更改文件并真正希望它强制重新加载.
这不是可行的解决方案。大量的浏览器只会拒绝缓存带有查询字符串的任何内容。这就是如果您对静态内容的引用具有查询字符串时,Google,GTMetrix和类似工具会引发标记的原因。虽然这肯定是一个不错的开发解决方案,但绝对不是生产解决方案。另外,浏览器控制缓存,而不是服务器。服务器只是建议何时刷新。浏览器不必收听服务器(通常不收听)。移动设备就是一个很好的例子。

3> Leopd..:

谷歌的apache mod_pagespeed插件会为你做自动版本控制.这真的很光滑.

它解析HTML从Web服务器出来(使用PHP,rails,python,静态HTML - 任何东西),并重写CSS,JS,图像文件的链接,因此它们包含一个id代码.它在修改后的URL上提供文件,并对它们进行非常长的缓存控制.文件更改后,它会自动更改URL,以便浏览器重新获取它们.它基本上只是工作,没有任何代码更改.它甚至会在出路时缩小你的代码.


当它显然是浏览器问题时,这是错误的(自动摆弄源代码).给我们(开发人员)一个真正的大脑擦除刷新: + F5
mod_pagespeed在功能上等同于html/css/js的完全自动构建/编译步骤.我认为你很难找到任何认为构建系统本质上是错误的认真的开发人员,或者认为它是完全自动的任何错误.干净构建的类比是清除mod_pagespeed的缓存:http://code.google.com/p/modpagespeed/wiki/FAQ#How_do_I_clear_the_cache_on_my_server?
@ T4NK3R mod_pagespeed不需要对你的源做任何事情来进行缓存管理,只是简单地提到它*可以*帮助缩小等内容.至于它是否"错",那完全是主观的.这对你来说可能是错的,但这并不意味着它是绝对的_bad_.
它也适用于nginx,但您必须从源代码构建它:https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source

4> levik..:

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

所以你的网址会是这样的

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

您仍然可以使用重写规则去除散列,但优点是现在您可以将缓存策略设置为"永久缓存",因为如果URL相同,则表示文件未更改.

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

每次CSS更改时,只需运行该脚本即可.浏览器只会在更改文件时重新加载.如果您进行编辑然后撤消它,那么确定您需要返回哪个版本以便访问者不要重新下载是没有意义的.


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

5> Phantom007..:

不确定为什么你们为实施这个解决方案付出了太多的痛苦.

如果获取文件的修改时间戳并将其作为查询字符串附加到文件,则需要执行的操作

在PHP中,我会这样做:


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


非常优雅,虽然我稍微将其修改为``,以防万一关于使用GET变量缓存URL的这个线程的参数(以建议的格式)是正确的

6> SCdF..:

您可以放在?foo=1234css/js导入的末尾,将1234更改为您喜欢的任何内容.看一下SO html源代码示例.

这个想法有吗?无论如何,请求都会丢弃/忽略参数,您可以在推出新版本时更改该数字.


注意:关于它如何影响缓存有一些争论.我相信它的一般要点是有或没有参数的GET请求应该是可缓存的,因此上述解决方案应该可行.

但是,由Web服务器决定是否要遵守规范的那一部分以及用户使用的浏览器,因为它可以直接进入并要求提供新版本.


值得注意的是,无论出于何种原因,Stackoverflow本身都使用查询字符串方法.
@troelskn:HTTP 1.1规范另有说明(关于带有查询参数的GET和HEAD请求):除非服务器提供明确的过期时间,否则缓存绝不能将对这些URI的响应视为新鲜.见http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
我尝试了所有主流浏览器的版本控制查询字符串类型,他们会缓存文件,规格与否.但是,我认为使用style.TIMESTAMP.css格式最好不要滥用查询字符串,因为缓存代理软件仍然可能不会缓存文件.
已经验证使用?=参数不会使浏览器在参数更改时重新获取缓存文件.唯一的方法是在服务器端以编程方式更改文件名本身,如Kip所回答

7> John Milliki..:

我听说这个叫做"自动版本控制".最常见的方法是在URL中的某处包含静态文件的mtime,并使用重写处理程序或URL confs将其删除:

也可以看看:

Django中的自动资产版本控制

自动版本您的CSS和JavaScript文件


谢谢,我想这是另一个案例,我的想法已经讨论过,我只是不知道它叫什么,所以我从来没有在谷歌搜索上找到它.

8> Michael Krop..:

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

想象一下,您是一个在浏览器中加载了SPA 版本M的用户:

    您的CD管道将应用程序的新版本N部署到服务器上

    您在SPA中导航,它将XHR发送到服务器以获取 /some.template

    (您的浏览器尚未刷新页面,因此您仍在运行M版本)

    服务器响应内容/some.template- 您是否希望它返回模板的版本MN

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

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

在初始页面加载后的某个时间异步请求资源

应用程序逻辑假设有关资源内容的事情(可能在将来的版本中有所改变)

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

    安装所有站点文件到版本迪尔斯:/v/…files…,/v/…files…

    设置HTTP标头以允许浏览器永久缓存文件

    (或者更好的是,将所有内容都放在CDN中)

    更新所有

    和CSS类似

    
    



    14> Michiel..:

    有趣的帖子.阅读完这里的所有答案,结合我从未遇到过"伪造"查询字符串的任何问题(我不确定为什么每个人都不愿意使用这个)我猜这个解决方案(不需要apache重写规则)如在接受的答案中)是计算CSS文件内容的短HASH(而不是文件日期时间)作为伪造的查询字符串.

    这将导致以下结果:

    
    

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



    15> Frank Bryce..:

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

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

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

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



    16> lony..:

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

    干杯谢谢.



    17> 小智..:

    尚未找到客户端DOM方法动态创建脚本节点(或css)元素:

    
    



    18> unknown123..:

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



    19> helios..:

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

    
    
    

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

    
    
    



    20> Walter Rumsb..:

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

    /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/etc.修订这意味着将注意到对CSS文件中引用的图像的更改.不与第一方案保证,即URL images/happy.gif相对/styles/screen.css?v=1235/styles/images/happy.gif不包含任何版本信息.

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



    21> Ponmudi VN..:

    你可以简单地用CSS/JS url添加一些随机数

    example.css?randomNo=Math.random()
    



    22> Ivan Kochurk..:

    对于ASP.NET,我认为下一个解决方案具有高级选项(调试/发布模式,版本):

    通过这种方式包含的Js或Css文件:

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
        
    jerry613
    这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有