1101

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

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

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


我发现John Millikinda5id 的建议很有用。事实证明有一个术语:自动版本控制

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

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

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

4

56 回答 56

483

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

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

首先,我们在 .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,因此它不会使用缓存的副本。

这也适用于图像、网站图标和 JavaScript。基本上任何不是动态生成的。

于 2008-09-23T03:07:49.363 回答
219

简单的客户端技术

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

您网站的一般访问者不会获得与您在开发网站时相同的体验。由于普通访问者访问该站点的频率较低(可能每月只有几次,除非您是 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 规范中规定的规则。当浏览器向服务器发出请求时,响应的一部分是一个Expires标头......一个告诉浏览器应该在缓存中保留多长时间的日期。下次浏览器遇到对同一文件的请求时,它会看到它在缓存中有一个副本,并查看到期日期来决定是否应该使用它。

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

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

于 2008-09-23T04:04:57.150 回答
115

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

它在离开网络服务器的过程中解析 HTML(适用于 PHP、Ruby on Rails、Python、静态 HTML ——任何东西)并重写指向 CSS、JavaScript、图像文件的链接,以便它们包含一个 id 代码。它在修改后的 URL 上提供文件,并对它们进行非常长的缓存控制。当文件更改时,它会自动更改 URL,因此浏览器必须重新获取它们。它基本上可以正常工作,无需对您的代码进行任何更改。它甚至会在退出时缩小您的代码。

于 2011-04-07T22:26:11.663 回答
96

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

所以你的网址会是这样的

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

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

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

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

于 2008-09-23T13:25:04.790 回答
89

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

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

在 PHP 中,我会这样做:

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

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

于 2013-01-26T10:53:34.730 回答
57

你可以?foo=1234在你的 CSS / JavaScript 导入的最后加上,把 1234 改成你喜欢的。以 Stack Overflow HTML 源代码为例。

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


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

但是,由网络服务器决定是否要遵守规范的那部分和用户使用的浏览器,因为它可以直接继续并要求一个新版本。

于 2008-09-23T03:12:49.427 回答
43

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

也可以看看:

于 2008-09-23T03:21:55.770 回答
32
于 2015-08-03T20:09:42.697 回答
17

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 输出(filemtimeUnix 时间戳的形式返回时间)

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
于 2017-03-03T10:15:11.377 回答
16

不要使用foo.css?version=1

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

于 2008-09-23T06:02:44.853 回答
12

Here is a pure JavaScript solution

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

})();

The above will look for the last time the user visited your site. If the last visit was before you released new code, it uses location.reload(true) to force page refresh from server.

I usually have this as the very first script within the <head> so it's evaluated before any other content loads. If a reload needs to occurs, it's hardly noticeable to the user.

I am using local storage to store the last visit timestamp on the browser, but you can add cookies to the mix if you're looking to support older versions of IE.

于 2015-01-13T14:46:12.453 回答
12

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

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

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
于 2010-08-05T16:55:01.247 回答
11

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

这将导致以下结果:

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

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

于 2009-06-24T23:20:10.143 回答
11

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

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

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

于 2014-09-30T20:11:38.663 回答
10

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

https://superuser.com/a/512833

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

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

于 2015-07-24T19:12:57.190 回答
8

感谢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;
            }
        }
    }
}
于 2011-01-07T05:58:27.343 回答
8

我还没有找到动态创建脚本节点(或 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>
于 2016-05-31T06:43:06.170 回答
7

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

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

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

于 2008-09-24T08:51:32.210 回答
7

Google Chrome 具有硬重新加载以及空缓存和硬重新加载选项。您可以单击并按住重新加载按钮(在Inspect Mode中)以选择一个。

于 2017-10-27T10:16:25.863 回答
6

我最近使用 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 %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 文件会自动触发缓存失效。它工作得很好,开销并不明显。

于 2008-09-23T13:54:40.803 回答
6

如果将会话 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>
于 2008-09-23T03:23:48.480 回答
6

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

example.css?randomNo = Math.random()
于 2013-07-04T10:01:51.350 回答
6

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

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

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

Global.JsPostfixGlobal.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();
    }
    ...
}
于 2013-08-03T13:28:49.630 回答
5

对于开发:使用浏览器设置:例如,Chrome network 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>
于 2019-06-17T18:11:06.297 回答
5

如果您使用的是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;
于 2016-12-23T16:25:21.033 回答
5

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

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

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

于 2017-09-12T06:40:52.297 回答
4

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

在 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;
    }
}
于 2018-07-31T06:06:16.600 回答
4

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

暂时删除缓存。

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

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

大多数小项目只是在玩——它们永远不会最终投入生产。所以对他们来说,你不需要更多...

因此,如果您使用Chrome DevTools,您可以按照下图所示的禁用缓存方法进行操作:

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

如果您有Firefox缓存问题:

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

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

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

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

于 2019-10-31T09:45:27.980 回答
4

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

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

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

关于其工作原理的更深入的解释是Web 缓存的工作原理

于 2016-07-07T16:50:33.593 回答
3

您可以使用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>
于 2019-05-15T03:02:34.213 回答
2

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

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

如果您使用的是 ASP.NET MVC,那么您可以在此处查看我的其他答案中的代码。

于 2011-06-22T12:46:04.343 回答
2

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

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

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

于 2013-06-11T12:16:51.897 回答
2

TomA 的回答是对的。

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

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

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

于 2012-07-06T02:50:39.193 回答
2

我建议实施以下过程:

  • 每次部署时对 CSS 和 JavaScript 文件进行版本控制。类似:screen.1233.css(如果您使用版本控制系统,该数字可以是您的 SVN 修订版)

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

于 2008-09-24T11:38:17.847 回答
2

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

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

于 2013-01-17T16:05:50.527 回答
2

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

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

于 2014-09-16T15:09:15.363 回答
2

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

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

?$Now.Format(dmYHis)

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

于 2015-07-20T20:05:11.050 回答
2

仅针对纯 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">
于 2018-02-11T02:25:30.003 回答
1

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

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

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

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

于 2013-03-29T13:51:04.680 回答
1

对 ASP.NET 网站的另一个建议,

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

  2. 对于 CSS 和 JavaScript 文件,在服务器上修改这些文件的机会很高,因此请将最小的cache-control:max-age值设置为 1 或 2 分钟或满足您的需要。

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

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

  5. 在随后的请求和两分钟后,我们看到对 CSS 和 JavaScript 文件的 304-Not Modified请求,这使我们避免了 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>
    
于 2014-08-12T13:19:18.070 回答
1

这里的许多答案都提倡在 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; do
    echo "$delim\"$l\": \"`md5 -q $l`\""
    delim=","
done
echo "}"

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

于 2014-12-12T19:54:04.643 回答
1

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

最后,我编写了一个快速页面(包括所有代码) ,作为发布过程的一部分,它可以自动转换 HTML/JavaScript index.html文件。它运行良好,仅根据上次修改日期更新新文件。

您可以在Autoversion your SPA index.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");
}
于 2016-05-12T08:20:43.190 回答
1

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

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

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

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

于 2018-10-05T18:12:08.487 回答
1

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

<script>
    var script = document.createElement('script');
    script.src = "js/app.js?v=" + Math.random();
    document.getElementsByTagName('head')[0].appendChild(script);
</script>
于 2020-03-17T13:45:14.193 回答
1

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

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

于 2020-09-17T05:55:17.713 回答
1

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

使用随机数或会话 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并滚动到设置首选项网络→ *禁用缓存(DevTools打开时)

Chrome 开发者工具

于 2018-09-07T20:14:01.827 回答
0

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

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

css-element.txt 的内容在哪里

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

因此,在您想链接到 my-new-css.css 或其他任何内容的那一天,您只需更改包含。

于 2008-09-25T01:40:46.137 回答
0

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

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

于 2020-03-31T20:51:00.807 回答
0

好吧,我已经通过在每次页面加载时通过向 JavaScript 文件版本添加随机数来更改 JavaScript 文件版本来实现它,如下所示:

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

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

<script src="file.js?version=<?php echo $random_number;?>"></script>
于 2016-08-21T02:59:01.733 回答
0

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

<script src="<?php echo get_template_directory_uri(); ?>/js/main.js?v=<?= time() ?>"></script>
于 2021-10-18T16:24:03.713 回答
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 也会更改,因此文件的版本会更改,并且在接下来打开浏览器时,它会检测到新文件并获取它。

于 2020-02-21T10:54:29.733 回答
0

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

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

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

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

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

于 2008-09-24T20:06:02.527 回答
-2

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

代替:

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

您可以使用:

$.ajaxSetup({
  cache: false
});

$.getScript('scripts/app.js'); // GET scripts/app.js?_1391722802668
于 2014-02-06T21:43:23.157 回答
-3

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

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

简而言之,如果您想要严格的缓存控制使用

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

在您的响应标题中。

于 2008-09-23T05:54:51.393 回答
-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 以外的其他工具,则根据语言会有一些变化,但几乎所有语言都有打印文件内容的方法。将它放在正确的位置(在该部分中),这样,您就不必依赖浏览器。

于 2011-11-03T17:34:42.217 回答