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

发布于 2024-07-06 10:34:36 字数 1051 浏览 9 评论 0原文

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

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

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


我发现 约翰米利金的da5id的建议很有用。 事实证明有一个术语:自动版本控制

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

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

但是,对于浏览器是否会缓存带有查询的文件存在一些讨论细绳。 (请记住,我们希望浏览器缓存该文件并在将来访问时使用它。我们只希望它在文件发生更改时再次获取该文件。)

I have noticed that some browsers (in particular, Firefox and Opera) are very zealous in using cached copies of .css and .js files, even between browser sessions. This leads to a problem when you update one of these files, but the user's browser keeps on using the cached copy.

What is the most elegant way of forcing the user's browser to reload the file when it has changed?

Ideally, the solution would not force the browser to reload the file on every visit to the page.


I have found John Millikin's and da5id's suggestion to be useful. It turns out there is a term for this: auto-versioning.

I have posted a new answer below which is a combination of my original solution and John's suggestion.

Another idea that was suggested by SCdF would be to append a bogus query string to the file. (Some Python code, to automatically use the timestamp as a bogus query string, was submitted by pi..)

However, there is some discussion as to whether or not the browser would cache a file with a query string. (Remember, we want the browser to cache the file and use it on future visits. We only want it to fetch the file again when it has changed.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

_蜘蛛 2024-07-13 10:34:36

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

原始 .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. works on all type of paths.
 */
function auto_version($file) {
  if($file[0] !== '/') {
    $file = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['PHP_SELF'])), '/') . '/' . $file;
  }
  
  if (!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。 基本上任何不是动态生成的东西。

This solution is written in PHP, but it should be easily adapted to other languages.

The original .htaccess regex can cause problems with files like json-1.3.js. The solution is to only rewrite if there are exactly 10 digits at the end. (Because 10 digits covers all timestamps from 9/9/2001 to 11/20/2286.)

First, we use the following rewrite rule in .htaccess:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Now, we write the following PHP function:

/**
 *  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. works on all type of paths.
 */
function auto_version($file) {
  if($file[0] !== '/') {
    $file = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['PHP_SELF'])), '/') . '/' . $file;
  }
  
  if (!file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
  return $file;
  
  $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
  return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}

Now, wherever you include your CSS, change it from this:

<link rel="stylesheet" href="/css/base.css" type="text/css" />

To this:

<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

This way, you never have to modify the link tag again, and the user will always see the latest CSS. The browser will be able to cache the CSS file, but when you make any changes to your CSS the browser will see this as a new URL, so it won't use the cached copy.

This can also work with images, favicons, and JavaScript. Basically anything that is not dynamically generated.

一影成城 2024-07-13 10:34:36

简单的客户端技术

一般来说,缓存是好的......因此有几种技术,具体取决于您是否在开发网站时自己解决问题,或者是否尝试在网站中控制缓存。生产环境。

您网站的一般访问者不会获得与您开发网站时相同的体验。 由于普通访问者访问该网站的频率较低(可能每个月只有几次,除非您是 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 标头,但我上面写的小技巧可能是一种更简单的方法。 由于缓存很好,您通常希望将该日期设置为遥远的未来(“远未来过期标头”),并使用上述技术来强制更改。

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

Simple Client-side Technique

In general, caching is good... So there are a couple of techniques, depending on whether you're fixing the problem for yourself as you develop a website, or whether you're trying to control cache in a production environment.

General visitors to your website won't have the same experience that you're having when you're developing the site. Since the average visitor comes to the site less frequently (maybe only a few times each month, unless you're a Google or hi5 Networks), then they are less likely to have your files in cache, and that may be enough.

If you want to force a new version into the browser, you can always add a query string to the request, and bump up the version number when you make major changes:

<script src="/myJavascript.js?version=4"></script>

This will ensure that everyone gets the new file. It works because the browser looks at the URL of the file to determine whether it has a copy in cache. If your server isn't set up to do anything with the query string, it will be ignored, but the name will look like a new file to the browser.

On the other hand, if you're developing a website, you don't want to change the version number every time you save a change to your development version. That would be tedious.

So while you're developing your site, a good trick would be to automatically generate a query string parameter:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

Adding a query string to the request is a good way to version a resource, but for a simple website this may be unnecessary. And remember, caching is a good thing.

It's also worth noting that the browser isn't necessarily stingy about keeping files in cache. Browsers have policies for this sort of thing, and they are usually playing by the rules laid down in the HTTP specification. When a browser makes a request to a server, part of the response is an Expires header... a date which tells the browser how long it should be kept in cache. The next time the browser comes across a request for the same file, it sees that it has a copy in cache and looks to the Expires date to decide whether it should be used.

So believe it or not, it's actually your server that is making that browser cache so persistent. You could adjust your server settings and change the Expires headers, but the little technique I've written above is probably a much simpler way for you to go about it. Since caching is good, you usually want to set that date far into the future (a "Far-future Expires Header"), and use the technique described above to force a change.

If you're interested in more information on HTTP or how these requests are made, a good book is "High Performance Web Sites" by Steve Souders. It's a very good introduction to the subject.

小猫一只 2024-07-13 10:34:36

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

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

Google's mod_pagespeed plugin for Apache will do auto-versioning for you. It's really slick.

It parses HTML on its way out of the webserver (works with PHP, Ruby on Rails, Python, static HTML -- anything) and rewrites links to CSS, JavaScript, image files so they include an id code. It serves up the files at the modified URLs with a very long cache control on them. When the files change, it automatically changes the URLs so the browser has to re-fetch them. It basically just works, without any changes to your code. It'll even minify your code on the way out too.

三人与歌 2024-07-13 10:34:36

我不知道为什么你们要这么痛苦地实施这个解决方案。

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

在 PHP 中,我会这样做:

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

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

I am not sure why you guys/gals are taking so much pain to implement this solution.

All you need to do if get the file's modified timestamp and append it as a querystring to the file.

In PHP I would do it as:

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

filemtime() is a PHP function that returns the file modified timestamp.

铁轨上的流浪者 2024-07-13 10:34:36

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

所以你的 URL 会是这样的

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

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

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

只需在每次 CSS 更改时运行该脚本即可。 浏览器只会在文件被更改时重新加载您的文件。 如果您进行编辑然后撤消它,那么您可以轻松地确定需要返回哪个版本以避免访问者重新下载。

Instead of changing the version manually, I would recommend you use an MD5 hash of the actual CSS file.

So your URL would be something like

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

You could still use the rewrite rule to strip out the hash, but the advantage is that now you can set your cache policy to "cache forever", since if the URL is the same, that means that the file is unchanged.

You can then write a simple shell script that would compute the hash of the file and update your tag (you'd probably want to move it to a separate file for inclusion).

Simply run that script every time CSS changes and you're good. The browser will ONLY reload your files when they are altered. If you make an edit and then undo it, there's no pain in figuring out which version you need to return to in order for your visitors not to re-download.

莫多说 2024-07-13 10:34:36

您可以将 ?foo=1234 放在 CSS / JavaScript 导入的末尾,将 1234 更改为您喜欢的任何值。 查看 StackOverflow HTML 源代码的示例。

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


注意:关于这到底如何影响缓存存在一些争论。 我相信它的总体要点是 GET 请求,带或不带参数应该是可缓存的,所以上面的解决方案应该可以工作。

然而,由网络服务器决定是否要遵守规范的该部分以及用户使用的浏览器,因为无论如何它都可以直接请求新版本。

You can just put ?foo=1234 at the end of your CSS / JavaScript import, changing 1234 to be whatever you like. Have a look at the Stack Overflow HTML source for an example.

The idea there being that the ? parameters are discarded / ignored on the request anyway and you can change that number when you roll out a new version.


Note: There is some argument with regard to exactly how this affects caching. I believe the general gist of it is that GET requests, with or without parameters should be cachable, so the above solution should work.

However, it is down to both the web server to decide if it wants to adhere to that part of the spec and the browser the user uses, as it can just go right ahead and ask for a fresh version anyway.

把回忆走一遍 2024-07-13 10:34:36

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

另请参阅:

I've heard this called "auto versioning". The most common method is to include the static file's modification time somewhere in the URL, and strip it out using rewrite handlers or URL configurations:

See also:

灵芸 2024-07-13 10:34:36

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

假设您是一位用户,浏览器中已加载 SPA M 版本:

  1. 您的 CD 管道将应用程序的新版本 N 部署到服务器上
  2. 您在 SPA 中导航,该 SPA 发送 XMLHttpRequest (XHR) 到服务器以获取 /some.template
  • (您的浏览器尚未刷新页面,因此您仍在运行版本M)
  1. 服务器响应 /some.template 的内容 - 您希望它返回版本 M 还是 N 模板?

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

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

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

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

  1. 将所有站点文件安装到版本化目录中:/v/…files…/v/…files…
  2. 设置HTTP 标头让浏览器永久缓存文件
  • (或者更好的是,将所有内容放入 CDN)
  1. 更新所有

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

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

The 30 or so existing answers are great advice for a circa 2008 website. However, when it comes to a modern, single-page application (SPA), it might be time to rethink some fundamental assumptions… specifically the idea that it is desirable for the web server to serve only the single, most recent version of a file.

Imagine you're a user that has version M of a SPA loaded into your browser:

  1. Your CD pipeline deploys the new version N of the application onto the server
  2. You navigate within the SPA, which sends an XMLHttpRequest (XHR) to the server to get /some.template
  • (Your browser hasn't refreshed the page, so you're still running version M)
  1. The server responds with the contents of /some.template — do you want it to return version M or N of the template?

If the format of /some.template changed between versions M and N (or the file was renamed or whatever) you probably don't want version N of the template sent to the browser that's running the old version M of the parser.†

Web applications run into this issue when two conditions are met:

  • Resources are requested asynchronously some time after the initial page load
  • The application logic assumes things (that may change in future versions) about resource content

Once your application needs to serve up multiple versions in parallel, solving caching and "reloading" becomes trivial:

  1. Install all site files into versioned directories: /v<release_tag_1>/…files…, /v<release_tag_2>/…files…
  2. Set HTTP headers to let browsers cache files forever
  • (Or better yet, put everything in a CDN)
  1. Update all <script> and <link> tags, etc. to point to that file in one of the versioned directories

That last step sounds tricky, as it could require calling a URL builder for every URL in your server-side or client-side code. Or you could just make clever use of the <base> tag and change the current version in one place.

† One way around this is to be aggressive about forcing the browser to reload everything when a new version is released. But for the sake of letting any in-progress operations to complete, it may still be easiest to support at least two versions in parallel: v-current and v-previous.

挽梦忆笙歌 2024-07-13 10:34:36

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

In Laravel (PHP) we can do it in the following clear and elegant way (using file modification timestamp):

<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>

And similar for CSS

<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">

Example HTML output (filemtime return time as as a Unix timestamp)

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
只涨不跌 2024-07-13 10:34:36

不要使用 foo.css?version=1

浏览器不应该缓存带有 GET 变量的 URL。 根据 http://www.thinkvitamin.com/features/webapps/serving- javascript-fast,尽管 Internet Explorer 和 Firefox 会忽略这一点,Opera 和 Safari 不会! 相反,请使用 foo.v1234.css,并使用重写规则来删除版本号。

Don’t 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 Internet Explorer and Firefox ignore this, Opera and Safari don't! Instead, use foo.v1234.css, and use rewrite rules to strip out the version number.

2024-07-13 10:34:36

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

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

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

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.

热血少△年 2024-07-13 10:34:36

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

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

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

The RewriteRule needs a small update for JavaScript or CSS files that contain a dot notation versioning at the end. E.g., json-1.3.js.

I added a dot negation class [^.] to the regex, so .number. is ignored.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
乙白 2024-07-13 10:34:36

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

这将导致以下结果:

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

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

Interesting post. Having read all the answers here combined with the fact that I have never had any problems with "bogus" query strings (which I am unsure why everyone is so reluctant to use this) I guess the solution (which removes the need for Apache rewrite rules as in the accepted answer) is to compute a short hash of the CSS file contents (instead of the file datetime) as a bogus querystring.

This would result in the following:

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

Of course, the datetime solutions also get the job done in the case of editing a CSS file, but I think it is about the CSS file content and not about the file datetime, so why get these mixed up?

2024-07-13 10:34:36

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

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

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

For ASP.NET 4.5 and greater you can use script bundling.

The request http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 is for the bundle AllMyScripts and contains a query string pair v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. The query string v has a value token that is a unique identifier used for caching. As long as the bundle doesn't change, the ASP.NET application will request the AllMyScripts bundle using this token. If any file in the bundle changes, the ASP.NET optimization framework will generate a new token, guaranteeing that browser requests for the bundle will get the latest bundle.

There are other benefits to bundling, including increased performance on first-time page loads with minification.

锦欢 2024-07-13 10:34:36

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

https://superuser.com/a/512833

打开开发者工具后,只需长按刷新按钮并在悬停后松开即可超过“清空缓存和硬重新加载”。

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

For my development, I find that Chrome has a great solution.

https://superuser.com/a/512833

With developer tools open, simply long click the refresh button and let go once you hover over "Empty Cache and Hard Reload".

This is my best friend, and is a super lightweight way to get what you want!

懒的傷心 2024-07-13 10:34:36

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

Thanks to Kip for his perfect solution!

I extended it to use it as an Zend_view_Helper. Because my client run his page on a virtual host I also extended it for that.

/**
 * 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;
            }
        }
    }
}
↙温凉少女 2024-07-13 10:34:36

我还没有找到动态创建脚本节点(或 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>

I have not found the client-side DOM approach creating the script node (or CSS) element dynamically:

<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>
暗地喜欢 2024-07-13 10:34:36

假设您有一个文件位于:

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

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

Say you have a file available at:

/styles/screen.css

You can either append a query parameter with version information onto the URI, e.g.:

/styles/screen.css?v=1234

Or you can prepend version information, e.g.:

/v/1234/styles/screen.css

IMHO, the second method is better for CSS files, because they can refer to images using relative URLs which means that if you specify a background-image like so:

body {
    background-image: url('images/happy.gif');
}

Its URL will effectively be:

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

This means that if you update the version number used, the server will treat this as a new resource and not use a cached version. If you base your version number on the Subversion, CVS, etc. revision this means that changes to images referenced in CSS files will be noticed. That isn't guaranteed with the first scheme, i.e. the URL images/happy.gif relative to /styles/screen.css?v=1235 is /styles/images/happy.gif which doesn't contain any version information.

I have implemented a caching solution using this technique with Java servlets and simply handle requests to /v/* with a servlet that delegates to the underlying resource (i.e. /styles/screen.css). In development mode I set caching headers that tell the client to always check the freshness of the resource with the server (this typically results in a 304 if you delegate to Tomcat's DefaultServlet and the .css, .js, etc. file hasn't changed) while in deployment mode I set headers that say "cache forever".

无声静候 2024-07-13 10:34:36

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

example.css?randomNo = Math.random()

You could simply add some random number with the CSS and JavaScript URL like

example.css?randomNo = Math.random()
最偏执的依靠 2024-07-13 10:34:36

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

Google Chrome has the Hard Reload as well as the Empty Cache and Hard Reload option. You can click and hold the reload button (in Inspect Mode) to select one.

终陌 2024-07-13 10:34:36

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

I recently solved this using Python. Here is the code (it 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 %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 content again, touching the CSS file will automatically trigger a cache invalidation. It works very well and the overhead is not noticeable.

撩人痒 2024-07-13 10:34:36

如果您将会话 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>

You can force a "session-wide caching" if you add the session-id as a spurious parameter of the JavaScript/CSS file:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

If you want a version-wide caching, you could add some code to print the file date or similar. If you're using Java you can use a custom-tag to generate the link in an elegant way.

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
半衾梦 2024-07-13 10:34:36

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

For ASP.NET I propose the following solution with advanced options (debug/release mode, versions):

Include JavaScript or CSS files this way:

<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 are 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();
    }
    ...
}
月光色 2024-07-13 10:34:36

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

If you're using Git and PHP, you can reload the script from the cache each time there is a change in the Git repository, using the following code:

exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'></script>'.PHP_EOL;
谢绝鈎搭 2024-07-13 10:34:36

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

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

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

Simply add this code where you want to do a hard reload (force the browser to reload cached CSS and JavaScript files):

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

Do this inside the .load, so it does not refresh like a loop.

苦行僧 2024-07-13 10:34:36

对于开发:使用浏览器设置:例如,Chrome network 选项卡 有一个禁用缓存 选项。

对于生产:使用服务器端渲染框架或纯 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>

For development: use a browser setting: for example, Chrome network tab has a disable cache option.

For production: append a unique query parameter to the request (for example, q?Date.now()) with a server-side rendering framework or pure JavaScript code.

// 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>
猥︴琐丶欲为 2024-07-13 10:34:36

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

暂时删除缓存。

“保持缓存与文件一致” ..这太麻烦了..

一般来说,我不介意加载更多 - 甚至在大多数项目上再次加载未更改的文件 - 实际上是无关紧要。 在开发应用程序时 - 我们主要从 localhost:port 上的磁盘加载 - 因此这个网络流量增加问题不是一个破坏交易的问题

大多数小项目只是玩玩——它们永远不会最终投入生产。 因此,对于他们来说,您不需要更多的东西...

因此,如果您使用 Chrome DevTools,您可以遵循这种禁用缓存方法,如下图所示:

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

如果您有 Firefox 缓存问题:

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

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

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

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

For developers with this problem while developing and testing:

Remove caching briefly.

"keep caching consistent with the file" .. it's way too much hassle ..

Generally speaking, I don't mind loading more - even loading again files which did not change - on most projects - is practically irrelevant. While developing an application - we are mostly loading from disk, on localhost:port - so this increase in network traffic issue is not a deal breaking issue.

Most small projects are just playing around - they never end-up in production. So for them you don't need anything more...

As such if you use Chrome DevTools, you can follow this disable-caching approach like in the image below:

How to force chrome to reload cached files

And if you have Firefox caching issues:

How to force asset reload on Firefox

How to disable caching in Firefox while in development

Do this only in development. You also need a mechanism to force reload for production, since your users will use old cache invalidated modules if you update your application frequently and you don't provide a dedicated cache synchronisation mechanism like the ones described in the answers above.

Yes, this information is already in previous answers, but I still needed to do a Google search to find it.

榕城若虚 2024-07-13 10:34:36

似乎这里的所有答案都表明命名方案中存在某种版本控制,这有其缺点。

浏览器应该通过读取 Web 服务器的响应(特别是 HTTP 标头)来清楚地了解要缓存的内容和不缓存的内容 - 该资源的有效期限是多长时间? 该资源自我上次检索以来是否已更新? 等等。

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

有关其工作原理的更深入说明请参阅Web 缓存工作原理

It seems all answers here suggest some sort of versioning in the naming scheme, which has its downsides.

Browsers should be well aware of what to cache and what not to cache by reading the web server's response, in particular the HTTP headers - for how long is this resource valid? Was this resource updated since I last retrieved it? etc.

If things are configured 'correctly', just updating the files of your application should (at some point) refresh the browser's caches. You can for example configure your web server to tell the browser to never cache files (which is a bad idea).

A more in-depth explanation of how that works is in How Web Caches Work.

南街九尾狐 2024-07-13 10:34:36

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

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

Just use server-side code to add the date of the file... that way it will be cached and only reloaded when the file changes.

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

This can be simplified to:

<script src="<%= Page.ResolveClientUrlUnique("~/js/custom.js") %>" type="text/javascript"></script>

By adding an extension method to your project to extend 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;
    }
}
半衬遮猫 2024-07-13 10:34:36

您可以使用 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>

You can use SRI to break the browser cache. You only have to update your index.html file with the new SRI hash every time. When the browser loads the HTML and finds out the SRI hash on the HTML page didn't match that of the cached version of the resource, it will reload your resource from your servers. It also comes with a good side effect of bypassing cross-origin read blocking.

<script src="https://jessietessie.github.io/google-translate-token-generator/google_translate_token_generator.js" integrity="sha384-muTMBCWlaLhgTXLmflAEQVaaGwxYe1DYIf2fGdRkaAQeb4Usma/kqRWFWErr2BSi" crossorigin="anonymous"></script>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文