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

时间:2008-09-23 03:07:14

标签: javascript css caching auto-versioning

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

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

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

更新:

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

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

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

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

55 个答案:

答案 0 :(得分:438)

更新:重写以纳入 John Millikin da5id 的建议。此解决方案是用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" />

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

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

答案 1 :(得分:179)

简单的客户端技术

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

您网站的常规访问者将无法获得您在开发网站时所拥有的相同体验。由于普通访问者访问网站的频率较低(可能每月只有几次,除非您是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的“高性能网站”。这是对这个主题的非常好的介绍。

答案 2 :(得分:112)

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

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

答案 3 :(得分:90)

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

所以你的网址就像

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

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

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

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

答案 4 :(得分:61)

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

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

在PHP中我会这样做:

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

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

答案 5 :(得分:51)

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

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


注意:关于这究竟如何影响缓存,有一些争论。我相信它的一般要点是GET请求,无论参数是否都可以缓存,所以上面的解决方案应该可行。

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

答案 6 :(得分:39)

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

另见:

答案 7 :(得分:24)

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

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

  1. 您的CD管道将应用程序的新版本 N 部署到服务器
  2. 您在SPA中导航,SPA会将XHR发送到服务器以获取/some.template
    • (您的浏览器尚未刷新页面,因此您仍在运行 M 版本)
  3. 服务器响应/some.template的内容 - 您是否希望它返回模板的 M N 版本?
  4. 如果/v<release_tag_1>/…files…的格式在版本 M N 之间发生了变化(或者文件已重命名或其他任何内容)您可能不会我希望发送到运行解析器旧版本 M 的浏览器的模板 N 。†

    满足两个条件时,网络应用会遇到此问题:

    • 在初始页面加载后的某个时间异步请求资源
    • 应用程序逻辑假定有关资源内容的事情(可能在将来的版本中有所改变)

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

    1. 将所有网站文件安装到版本化的目录中:/v<release_tag_2>/…files…<script>
    2. 设置HTTP标头,让浏览器永久缓存文件
      • (或者更好的是,把所有东西放在CDN中)
    3. 更新所有<link>和{{1}}标记等,以指向其中一个版本化目录中的该文件
    4. 最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个URL调用URL构建器。或者您可以巧妙地使用<base> tag并在一个地方更改当前版本。

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

答案 8 :(得分:14)

不要使用foo.css?version = 1!浏览器不应该使用GET变量缓存URL。根据{{​​3}},虽然IE和Firefox忽略了这一点,但Opera和Safari却没有!相反,请使用foo.v1234.css,并使用重写规则去除版本号。

答案 9 :(得分:10)

对于ASP.NET 4.5及更高版本,您可以使用script bundling

  

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

捆绑还有其他好处,包括首次加载页面时提高性能。

答案 10 :(得分:10)

RewriteRule需要对js或css文件进行小的更新,这些文件最后包含点符号版本控制。例如。 JSON-1.3.js。

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

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

答案 11 :(得分:8)

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

答案 12 :(得分:8)

有趣的帖子。阅读完这里的所有答案并结合我从未遇到过“伪造”查询字符串的问题(我不确定为什么每个人都不愿意使用它)我想这个解决方案(这消除了对apache重写规则的需求)如在接受的答案中)是计算CSS文件内容的短HASH(而不是文件datetime)作为伪造的查询字符串。

这将产生以下结果:

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

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

答案 13 :(得分:8)

这是一个纯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。

答案 14 :(得分:6)

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

}

干杯谢谢。

答案 15 :(得分:6)

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

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

答案 16 :(得分: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();
    }
    ...
}

答案 17 :(得分:5)

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

example.css?randomNo=Math.random()

答案 18 :(得分:5)

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

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

答案 19 :(得分: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的网址/styles/screen.css?v=1235/styles/images/happy.gif,不包含任何版本信息。

我已经使用Java servlet实现了一种使用此技术的缓存解决方案,并使用委托给底层资源(即/v/*)的servlet简单地处理对/styles/screen.css的请求。在开发模式中,我设置了缓存标头,告诉客户端始终使用服务器检查资源的新鲜度(如果您委托给Tomcat的DefaultServlet.css,{{1}通常会产生304在部署模式下,我设置了标题为“永远缓存”的文件。

。}等等文件没有改变

答案 20 :(得分:4)

谷歌浏览器具有硬重新加载以及清空缓存和硬重新载入选项。您可以单击并按住重新加载按钮(在检查模式下)以选择一个。 / p>

答案 21 :(得分: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文件会自动触发缓存失效。效果很好,开销也不明显。

答案 22 :(得分:4)

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

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

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

这是我最好的朋友,是一种超轻量级的方式来获得你想要的东西!

答案 23 :(得分:2)

我将此答案添加为SilverStripe http://www.silverstripe.org具体的答案,我一直在寻找并且从未找到,但已经从阅读中得到了解答:http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110

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

?$Now.Format(dmYHis)

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

答案 24 :(得分:2)

如果您使用的是现代浏览器,则可以使用清单文件通知浏览器需要更新哪些文件。这不需要标题,网址中没有版本......

有关详细信息,请参阅: 请参阅:https://developer.mozilla.org/nl/docs/Web/HTML/Applicatie_cache_gebruiken#Introduction

答案 25 :(得分:2)

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

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

答案 26 :(得分:2)

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

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

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

这里有更深入的解释 https://www.mnot.net/cache_docs/#WORK

答案 27 :(得分:2)

对于Java Servlet环境,您可以查看Jawr library。功能页面解释了它如何处理缓存:

  

Jawr将尽力强迫您的客户缓存资源。如果浏览器询问文件是否发生更改,则会返回304(未修改)标头而不包含任何内容。另一方面,使用Jawr,您将100%确定所有客户端都下载了新版本的捆绑包。资源的每个URL都将包含一个自动生成的基于内容的前缀,该前缀会在更新resurce时自动更改。部署新版本后,捆绑包的URL也会发生变化,因此客户端将无法使用较旧的缓存版本。

该库也可以进行js / css缩小,但如果你不想要它,可以关闭它。

答案 28 :(得分:2)

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

答案 29 :(得分:2)

很抱歉带回了一个死线程。

@ TomA是对的。

使用“查询字符串”方法不会被下面的Steve Souders引用缓存:

  

... Squid是一种流行的代理,它不会缓存资源   查询字符串。

@ TomA建议使用style.TIMESTAMP.css是好的,但是MD5会好得多,因为只有当内容真正改变时,MD5才会改变。

答案 30 :(得分:2)

只需添加此代码,即可进行硬重载(强制浏览器重新加载缓存的CSS / JS文件) 在.load中执行此操作,因此它不会像循环一样刷新

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

答案 31 :(得分:2)

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

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

如果您正在使用ASP.NET MVC,那么您可以查看代码in my other answer here

答案 32 :(得分:2)

我建议实施以下流程:

  • 在部署时版本你的css / js文件,例如:screen.1233.css(如果你使用版本控制系统,这个数字可以是你的SVN版本)

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

答案 33 :(得分:1)

在纯JS

中禁用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">

答案 34 :(得分:1)

ASP.Net网站的另一个建议,

  1. 为不同的静态文件设置不同的缓存控制:max-age值。
  2. 对于css / js文件,在服务器上修改这些文件的可能性很高,因此请设置最小缓存控制:max-age值为1或2分钟或满足您需要的内容。
  3. 对于图像,将远程日期设置为缓存控制:max-age值,比如说360天。
  4. 通过这样做,当我们发出第一个请求时,所有静态内容都会以200-OK响应下载到客户端计算机。
  5. 在后续请求中,两分钟后,我们会在css和js文件上看到304-Not Modified请求,这些请求可以避免我们进行css / js版本控制。
  6. 将不会请求图像文件,因为它们将从缓存内存中使用,直到缓存过期。
  7. 通过使用下面的web.config配置,我们可以实现上述行为,                                                                                                 

答案 35 :(得分:1)

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

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

例如,这是一个简单的bash脚本,它从stdin中读取文件名列表,并将包含哈希的json文件写入stdout:

#!/bin/bash
# create a json map from filenames to md5s
# run as hashes.sh < inputfile.list > outputfile.json

echo "{"
delim=""
while read l; do
    echo "$delim\"$l\": \"`md5 -q $l`\""
    delim=","
done
echo "}"

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

答案 36 :(得分:1)

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

最后,我写了一个快速页面(包括所有代码),作为发布过程的一部分,自动转换html / js index.html。它工作正常,只根据上次修改日期更新新文件。

您可以在http://blueskycont.com/wp/2016/05/12/autoversion-your-spa-index-html/看到我的帖子。那里也有免费工作的winapp。

代码的内容是

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

答案 37 :(得分:1)

“SCdF建议的另一个想法是将伪造的查询字符串附加到文件中。(某些Python代码自动使用时间戳作为虚假查询字符串由pi提交。)但是,有一些讨论作为浏览器是否会使用查询字符串缓存文件。(请记住,我们希望浏览器缓存该文件并在以后的访问中使用它。我们只希望它在更改后再次获取文件。)因为它不清楚虚假查询字符串会发生什么,我不接受这个答案。“

&lt; link rel =“stylesheet”href =“file.css?&lt;?= hash_hmac('sha1',session_id(),md5_file(”file.css“));?&gt;” /&GT;

散列文件意味着更改后,查询字符串将发生变化。如果没有,它将保持不变。每个会话都会强制重新加载。

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

答案 38 :(得分:1)

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

要解决此问题,您必须使用no-cache标头或元保护请求页面,以确保在每次加载时都刷新它。或者您必须维护您在服务器上部署的样式文件的所有版本,每个版本作为单个文件并且其差异器完好无损,以便请求页面可以获取样式文件的版本它是专为。在后一种情况下,您基本上将HTML页面和样式表的版本绑定在一起,这可以静态完成,不需要任何服务器逻辑。

答案 39 :(得分:0)

在ASP.NET Core中,您可以通过添加“ asp-append-version”来实现:

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

 <script src="~/js/xxx.js" asp-append-version="true"></script>

它将生成HTML:

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

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

答案 40 :(得分:0)

我已经通过使用解决了这个问题 ETag

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

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

答案 41 :(得分:0)

这是我基于Bash脚本的缓存清除解决方案:

  1. 我假设您在 index.html 文件
  2. 中引用了CSS和JavaScript文件
  3. 添加时间戳作为 index.html 中.js和.css的参数,如下所示(仅一次)
  4. 使用上述时间戳创建一个 timestamp.txt 文件。
  5. 对.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.html
echo "$current_timestamp" >timestamp.txt

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

答案 42 :(得分:0)

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

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

答案 43 :(得分:0)

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

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

答案 44 :(得分:0)

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

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

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

通常来说,我不介意加载更多-甚至再次加载在大多数项目中没有变化的文件-实际上是无关紧要的。在开发应用程序时-我们主要是从磁盘上加载localhost:port上的-因此此increase in network traffic问题不是破坏交易的问题

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

因此,如果您使用 Chrome开发工具,则可以按照以下图片所示使用这种禁用缓存的方法: how to force chrome to reload cached files

如果您有 firefox 缓存问题: how to force asset reload on firefox

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

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

希望这个答案很清楚,现在您不需要了。

答案 45 :(得分:0)

大多数答案都说明了如何使用mvc或服务器渲染框架进行操作-但您可以使用纯JavaScript来做到这一点,不幸的是,您需要两个脚本标签

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

答案 46 :(得分:0)

  

我知道的最快,最快捷的方法之一就是更改您所在文件夹的名称   有CSS或JS文件。   或针对开发人员。   更改CSS / js文件的名称,例如版本。

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

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

答案 47 :(得分:0)

现有答案带来的小改进...

使用随机数或会话ID将导致在每个请求上重新加载。理想情况下,只有在任何js / css文件中进行了一些代码更改后,我们才可能需要更改。 当使用Common JSP文件作为许多其他jsp和js文件的模板时 在普通的JSP文件中添加以下内容

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var = "version" scope = "application" value = "1.0.0" />

现在在js文件包含物中的以下所有位置使用上述变量。

<script src='<spring:url value="/js/myChangedFile.js?version=${version}"/>'></script>

优势:

  

1)这种方法将帮助您仅在一个位置更改版本号。

     

2)保持适当的版本号(通常是内部版本号/发行版号)将有助于您检查/验证是否正确部署了代码更改。(从浏览器的开发者控制台。

另一个有用的提示:

如果您使用的是Chrome浏览器,则可以在打开开发工具时禁用缓存。 在Chrome浏览器中,按F12> F1滚动到“设置”>“首选项”>“网络”>“禁用缓存(在打开DevTools的情况下)

Chrome DevTools

答案 48 :(得分:0)

我的方法是将链接元素放入服务器端包括:

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

其中css-element.txt的内容为

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

所以当你想要链接到my-new-css.css或其他什么时,你只需更改包含。

答案 49 :(得分:0)

好吧,我通过在每次页面加载时更改js版本,通过向js文件版本添加随机数来实现它的工作方式如下:

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

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

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

答案 50 :(得分:-1)

您可以使用SRI中断浏览器缓存。您只需每次都使用新的SRI哈希值更新index.html。当浏览器加载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>

答案 51 :(得分:-1)

如果您使用的是jquery,则会有一个名为cache的选项会附加一个随机数 这不是我知道的完整答案,但它可能会节省你一些时间

答案 52 :(得分:-2)

我没有看到js文件提及的另一种方法是将jQuery $.getScript$.ajaxSetup选项cache: false结合使用。

而不是:

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

您可以使用:

$.ajaxSetup({
  cache: false
});

$.getScript('scripts/app.js'); // GET scripts/app.js?_1391722802668

答案 53 :(得分:-3)

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

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

简而言之,如果你想要使用紧密的缓存控制

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

在您的回复标题中。

答案 54 :(得分:-3)

最简单的方法是利用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之外的其他内容,则根据语言的不同有一些变化,但几乎所有语言都有办法打印文件的内容。将它放在正确的位置(在部分中),这样,您就不必依赖浏览器。