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

2020/09/18 22:01 · javascript ·  · 0评论

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

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

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

更新:

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

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

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

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

更新: 重写以合并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。基本上任何不是动态生成的。

简单的客户端技术

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

网站的一般访问者不会拥有与开发网站时相同的体验。由于一般访问者访问该网站的频率较低(除非您是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”。这是对该主题的很好的介绍。

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

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

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

因此,您的网址将类似于

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

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

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

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

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

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

在PHP中,我会这样做:

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

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

您只需将?foo=1234css / js导入的末尾放置,将1234更改为所需的值即可。看看SO html源代码的示例。

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


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

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

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

也可以看看:

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

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

  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。

Dont use foo.css?version=1! Browsers aren't supposed to cache URLs with GET variables. According to http://www.thinkvitamin.com/features/webapps/serving-javascript-fast, though IE and Firefox ignore this, Opera and Safari don't! Instead, use foo.v1234.css, and use rewrite rules to strip out the version number.

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

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

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

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

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

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

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

这是一个纯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添加到混合中。

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

这将导致以下结果:

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

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

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

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

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

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

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

}

干杯,谢谢。

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

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

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

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

/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文件中引用的图像更改。不与第一方案保证,即URL images/happy.gif相对/styles/screen.css?v=1235就是/styles/images/happy.gif不包含任何版本信息。

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

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

example.css?randomNo=Math.random()

对于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 and Global.CssPostfix is calculated by the following way in 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();
    }
    ...
}

I recently solved this using Python. Here the code (should be easy to adopt to other languages):

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) 

This code basically appends the files time-stamp as a query parameter to the URL. The call of the following function

script("/main.css")

will result in

<link rel="stylesheet" type="text/css"  href="/main.css?1221842734">

The advantage of course is that you do never have to change your html again, touching the CSS file will automatically trigger a cache invalidation. Works very good and the overhead is not noticeable.

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

如果您是希望避免缓存的开发人员,则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>

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

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

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

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

只需在您要进行硬重装的地方添加此代码(强制浏览器重载已缓存的CSS / JS文件)即可

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

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

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

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

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

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

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

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

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

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

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

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

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

我建议执行以下过程:

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

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

本文地址:http://javascript.askforanswer.com/ruheqiangzhiliulanqizhongxinjiazaihuancundecss-jswenjian.html
文章标签: ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!