如何强制浏览器重新加载caching的CSS / JS文件?
我注意到一些浏览器(特别是Firefox和Opera)非常热衷于使用.css和.js文件的caching副本,即使在浏览器会话之间。 当您更新这些文件中的一个时,这会导致问题,但用户的浏览器不断使用caching副本。
问题是:什么是最强大的方式强迫用户的浏览器重新加载文件时,它已经改变?
理想情况下,解决scheme不会强制浏览器在每次访问页面时重新加载文件。 我会张贴自己的解决scheme作为答案,但我很好奇,如果任何人有更好的解决scheme,我会让你的选票决定。
更新:
在经过一段时间的讨论之后,我发现John Millikin和Da5id的build议是有用的。 原来这里有一个术语: auto-versioning 。
我已经发布了一个新的答案,下面是我原来的解决scheme和约翰的build议的组合。
SCdF提出的另一个想法是将伪造的查询string追加到文件中。 (一些Python代码自动使用时间戳作为伪造查询string由pi提交)。 但是,关于浏览器是否会用查询stringcaching文件还有一些讨论。 (请记住,我们希望浏览器caching文件并在将来的访问中使用它,我们只希望在文件更改时再次获取文件。)
由于不清楚假冒的查询string会发生什么,我不接受这个答案。
更新:重写包含John Millikin和da5id的build议。 这个解决scheme是用PHP编写的,但是应该很容易适应其他语言。
更新2: json-1.3.js
·约翰逊的评论,原来的.htaccess
正则expression式可能会导致像json-1.3.js
文件的问题。 解决办法是只在最后有十位数的情况下重写。 (因为10位数字涵盖了从9/9/2001到11/20/2286的所有时间戳。)
首先,我们在.htaccess中使用以下重写规则:
RewriteEngine on RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]
现在我们编写如下的PHP函数:
/** * Given a file, ie /css/base.css, replaces it with a string containing the * file's mtime, ie /css/base.1221534296.css. * * @param $file The file to be loaded. Must be an absolute path (ie * 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。 浏览器将能够cachingCSS文件,但是当您对CSS进行任何更改时,浏览器会将其视为新的URL,因此不会使用caching副本。
这也可以处理图片,网站图标和JavaScript。 基本上任何不是dynamic生成的。
简单的客户端技术
一般来说,caching是好的。所以有几种技术,这取决于你在开发一个网站时是否为自己解决问题,或者你是否试图在生产环境中控制caching。
一般访客到您的网站将不会有您在开发网站时所具有的相同的经验。 由于普通用户访问网站的频率较低(可能只有每月几次,除非你是Google或hi5networking),那么他们不太可能将你的文件放在caching中,这可能就足够了。 如果你想强制一个新的版本进入浏览器,你可以随时在请求中添加一个查询string,当你进行重大更改时,可以修改版本号:
<script src="/myJavascript.js?version=4"></script>
这将确保每个人都获得新的文件。 它的工作原理是浏览器查看文件的URL,以确定它是否有caching中的副本。 如果你的服务器没有设置对查询string做任何事情,它将被忽略,但是这个名字看起来像一个新的文件给浏览器。
另一方面,如果您正在开发一个网站,那么每次保存对开发版本的更改时都不想更改版本号。 那将是乏味的。
所以,当你开发你的网站时,一个好的技巧是自动生成一个查询string参数:
<!-- Development version: --> <script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>
向请求中添加一个查询string是版本化资源的好方法,但是对于一个简单的网站来说,这可能是不必要的。 请记住,caching是一件好事。
另外值得一提的是,浏览器并不一定把文件保存在caching中。 浏览器有这样的政策,他们通常是按照HTTP规范中规定的规则来玩的。 当浏览器向服务器发出请求时,响应的一部分是EXPIRES标头。这是一个告诉浏览器在caching中保存多久的date。 下一次浏览器遇到同一个文件的请求时,它会看到它在caching中有一个副本,并查看EXPIRESdate以决定是否应该使用它。
所以不要相信,它实际上是你的服务器,使浏览器caching如此持久。 你可以调整你的服务器设置并更改EXPIRES头文件,但是我上面写的小技巧对于你来说可能是一个更简单的方法。 由于caching是好的,你通常希望将这个date设置到将来(“未来期满头”),并使用上述技术来强制更改。
如果您对HTTP的更多信息感兴趣,或者这些请求是如何做出的,那么一本好书是Steve Souders的“高性能网站”。 这是一个很好的介绍这个问题。
Google的mod_pagespeed插件会为你做自动版本控制。 这真的很光滑。
它从web服务器(与PHP,rails,python,静态HTML – 任何东西)一起parsingHTML,并重写链接到CSS,JS,图像文件,使其包含一个ID代码。 它提供修改后的URL上的文件,并对其进行非常长的caching控制。 当文件改变时,它会自动更改URL,以便浏览器重新获取它们。 它基本上可以正常工作,不需要对代码进行任何修改。 它甚至会在出路的时候缩小你的代码。
我不build议手动更改版本,而是使用实际CSS文件的MD5散列。
所以你的URL会是这样的
http://mysite.com/css/[md5_hash_here]/style.css
您仍然可以使用重写规则去除散列,但是现在您可以将caching策略设置为“永久caching”,因为如果URL相同,则意味着文件不变。
然后,您可以编写一个简单的shell脚本来计算文件的散列并更新您的标记(您可能希望将其移至单独的文件以供包含)。
每次CSS改变时,简单地运行该脚本,你很好。 浏览器只会在文件被修改时重新加载文件。 如果您进行了编辑,然后撤消它,则无法确定您需要返回哪个版本,以避免您的访问者重新下载。
你可以把?foo=1234
放在你的css / js导入的结尾处,把1234改成任何你喜欢的。 看一个例子的SO html源代码。
那里的想法是, 参数在请求中被丢弃/忽略,当您推出新版本时,您可以更改该号码。
注意:关于这如何影响caching,有一些争论。 我相信一般的要点是GET请求有或没有参数应该是可以caching的,所以上面的解决scheme应该是可行的。
然而,Web服务器决定是否要遵守该规范的一部分和用户使用的浏览器,因为它可以直接前进并要求新版本。
不知道为什么你们这么做很难实施这个解决scheme。
您只需获取文件的修改时间戳并将其作为查询string追加到文件即可
在PHP中,我会这样做:
<link rel="stylesheet" href="mycss.css?v=<?php echo filemtime('mycss.css') ?>"/>
filemtime是一个返回文件修改时间戳的PHP函数。
我听说过这个叫“自动版本控制”。 最常见的方法是在URL中的某处包含静态文件的mtime,并使用重写处理程序或URLconfiguration文件将其除去:
也可以看看:
- Django自动资产版本控制
- 自动版本您的CSS和JavaScript文件
不要使用foo.css?版本= 1! 浏览器不应该使用GETvariablescachingURL。 据http://www.thinkvitamin.com/features/webapps/serving-javascript-fast ,虽然IE和Firefox忽略了这一点,Opera和Safari不! 而是使用foo.v1234.css,并使用重写规则去除版本号。
对于大约2008年的网站,30个左右的现有答案是很好的build议。 然而,当涉及到一个现代化的单页面应用程序 (SPA)时,重新思考一些基本的假设可能是时候了……特别是这样的想法:Web服务器只需要服务于单个最新版本的文件。
想象一下,你是一个将SPA的版本M加载到浏览器中的用户:
- 您的CDpipe道将应用程序的新版本N部署到服务器上
- 您可以在SPA中导航,然后将XHR发送到服务器以获取
/some.template
- (你的浏览器没有刷新页面,所以你仍然运行版本M )
- 服务器响应
/some.template
的内容 – 你想要它返回版本M或N的模板?
如果/some.template
的格式在版本M和N之间改变(或者文件被重命名或者其它), 你可能不希望模板的版本N被发送到运行parsing器的旧版本M的浏览器 。
满足两个条件时,Web应用程序会遇到此问题:
- 资源是在初始页面加载后的某个时间asynchronous请求的
- 应用程序逻辑假设有关资源内容的内容(可能会在将来的版本中更改)
一旦你的应用程序需要同时提供多个版本, 解决caching和“重新加载”就变得微不足道:
- 将所有站点文件安装到版本化的目录中:
/v<release_tag_1>/…files…
,/v<release_tag_2>/…files…
- 设置HTTP头让浏览器永远caching文件
- (或者更好的是,把所有东西放在CDN中)
- 更新所有
<script>
和<link>
标记等,以指向其中一个版本化目录中的文件
最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个URL调用URL构build器。 或者您可以巧妙地使用<base>
标签 ,并将当前版本更改为一个地方。
†解决这个问题的方法之一就是在发布新版本时强制浏览器重新加载所有内容。 但是为了让任何正在进行的操作完成,并行支持至less两个版本仍然是最简单的:v-current和v-previous。
RewriteRule需要对包含点符号版本的js或css文件进行小小的更新。 例如json-1.3.js。
我添加了一个点否定类[^。]到正则expression式.number。 被忽略。
RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
有趣的职位。 在阅读了所有的答案后,再加上我从来没有遇到任何与“假”查询string有关的问题(我不确定为什么每个人都不愿意使用这个string),我猜这个解决scheme(它不需要使用apache的重写规则正如在接受的答案中)是计算CSS文件内容(而不是文件date时间)的短HASH作为假查询string。
这将导致以下结果:
<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />
当然,date时间解决scheme也可以在编辑CSS文件的情况下完成工作,但我认为这是关于css文件内容而不是文件date时间,那么为什么要把这些混在一起呢?
对于ASP.NET 4.5及更高版本,您可以使用脚本捆绑 。
请求
http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81
用于包AllMyScripts,并包含查询string对v = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81。 查询stringv有一个值标记,它是用于caching的唯一标识符。 只要bundle不更改,ASP.NET应用程序将使用此令牌请求AllMyScripts软件包。 如果包中的任何文件发生更改,则ASP.NET优化框架将生成一个新的令牌,以确保浏览器对该包的请求将获得最新的包。
捆绑还有其他好处,包括在第一次加载页面时缩小性能。
如果将session-id添加为js / css文件的强壮参数,则可以强制执行“会话范围的caching”:
<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" /> <script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>
如果你想要一个全版本的caching,你可以添加一些代码来打印文件date或类似的。 如果您使用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'); }
其url将实际上是:
/v/1234/styleshttp://img.dovov.comhappy.gif
这意味着如果您更新所使用的版本号,服务器会将其视为新资源,而不是使用caching版本。 如果你的版本号是在Subversion / CVS / etc中。 这意味着更改CSS文件中引用的图像将被注意到。 这不能保证第一个scheme,即相对于/styles/screen.css?v=1235
的URL images/happy.gif
是/styleshttp://img.dovov.comhappy.gif
,它不包含任何版本信息。
我已经使用这种技术与Java servlet一起实现了一个caching解决scheme,并且简单地使用委托给底层资源(即/styles/screen.css
)的servlet处理对/v/*
的请求。 在开发模式中,我设置了caching头,告诉客户端总是用服务器检查资源的新鲜度(如果你委托给Tomcat的DefaultServlet
,并且.css
, .js
等文件没有改变,这通常会导致304 ),而在部署模式中,我设置标题,说“永远caching”。
感谢Kip的完美解决scheme!
我扩展它来使用它作为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; } } } }
欢呼和感谢。
这是一个纯粹的JavaScript解决scheme
(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添加到组合中。
我最近用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文件会自动触发caching失效。 工作非常好,开销不明显。
你可以简单地添加一些随机数与CSS / JS的url一样
example.css?randomNo=Math.random()
对于ASP.NET,我想下一个解决scheme具有高级选项(debugging/发布模式,版本):
通过这种方式包含的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(); } ... }
没有find客户端的DOM方法dynamic创build脚本节点(或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>
我build议实施以下stream程:
-
在你部署的时候版本你的css / js文件,如:screen.1233.css(如果你使用版本控制系统,这个数字可以是你的SVN版本)
-
缩小它们以优化加载时间
对于我的发展,我发现铬是一个很好的解决scheme。
https://developer.chrome.com/devtools/docs/tips-and-tricks#hard-reload
随着开发工具打开,只需长按一下刷新button,一旦你把鼠标hover在“清空caching和硬重新加载”上。
这是我最好的朋友,是一个超轻量级的方式来得到你想要的!
在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'))}}">
I put an MD5 hash of the file's contents in its URL. That way I can set a very long expiration date, and don't have to worry about users having old JS or CSS.
I also calculate this once per file at runtime (or on file system changes) so there's nothing funny to do at design time or during the build process.
If you're using ASP.NET MVC then you can check out the code in my other answer here .
If you are using a modern browser, you could use a manifest file to inform the browsers which files need to be updated. This requires no headers, no versions in urls etc…
For more details, see: See: https://developer.mozilla.org/nl/docs/Web/HTML/Applicatie_cache_gebruiken#Introduction
I'm adding this answer as a SilverStripe http://www.silverstripe.org specific answer which I was looking for and never found but have worked out from reading: http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110
Hopefully this will help someone using a SilverStripe template and trying to force reload a cached image on each page visit / refresh. In my case it is a gif animation which only plays once and therefor did not replay after it was cached. In my template I simply added:
?$Now.Format(dmYHis)
to the end of the file path to create a unique time stamp and to force the browser to treat it as a new file.
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 webservers response, in particular the http headers – for how long is this resource valid ? was this resource updated since I last retrieved it ? etcetera.
If things are configured 'correctly', just updating the files of your application should (at some point) refresh the browsers 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 here https://www.mnot.net/cache_docs/#WORK
google chrome has Hard Reload as well as Empty Cache and Hard Reload option.You can click and hold the reload button (In Inspect Mode) to select one .
My method to do this is simply to have the link element into a server-side include:
<!--#include virtual="/includes/css-element.txt"-->
where the contents of css-element.txt is
<link rel="stylesheet" href="mycss.css"/>
so the day you want to link to my-new-css.css or whatever, you just change the include.
Sorry for bringing back a dead thread.
@ TomA is right.
Using "querystring" method will not be cached as quoted by Steve Souders below:
…that Squid, a popular proxy, doesn't cache resources with a querystring.
@ TomA suggestion of using style.TIMESTAMP.css is good, but MD5 would be much better as only when the contents were genuinely changed, the MD5 changes as well.