Kip*_*Kip 957 javascript css caching auto-versioning
我注意到一些浏览器(特别是Firefox和Opera)非常热衷于使用.css和.js文件的缓存副本,即使在浏览器会话之间也是如此.当您更新其中一个文件但用户的浏览器继续使用缓存副本时,这会导致问题.
问题是:在更改文件时,强制用户浏览器重新加载文件的最优雅方法是什么?
理想情况下,解决方案不会强制浏览器在每次访问页面时重新加载文件.我会发布自己的解决方案作为答案,但我很好奇,如果有人有更好的解决方案,我会让你的投票决定.
更新:
在这里讨论了一段时间后,我发现John Millikin和da5id的建议很有用.事实证明,有一个术语:自动版本控制.
我在下面发布了一个新的答案,它是我原来的解决方案和John的建议的组合.
SCdF建议的另一个想法是将伪造的查询字符串附加到文件中.(一些Python代码自动使用时间戳作为伪造的查询字符串由pi提交.).但是,有一些关于浏览器是否会使用查询字符串缓存文件的讨论.(请记住,我们希望浏览器缓存文件并在以后的访问中使用它.我们只希望它在更改后再次获取文件.)
由于不清楚伪造的查询字符串会发生什么,我不接受这个答案.
Kip*_*Kip 446
更新: 重写以纳入John Millikin和da5id的建议.此解决方案是用PHP编写的,但应该很容易适应其他语言.
更新2:结合Nick Johnson的评论,原始.htaccess
正则表达式可能会导致文件出现问题json-1.3.js
.解决方案是仅在末尾恰好有10位数时才重写.(因为10位数字涵盖了从9/9/2001到11/20/2286的所有时间戳.)
首先,我们在.htaccess中使用以下重写规则:
RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]
Run Code Online (Sandbox Code Playgroud)
现在,我们编写以下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);
}
Run Code Online (Sandbox Code Playgroud)
现在,无论您何时包含CSS,请从以下位置进行更改:
<link rel="stylesheet" href="/css/base.css" type="text/css" />
Run Code Online (Sandbox Code Playgroud)
对此:
<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />
Run Code Online (Sandbox Code Playgroud)
这样,您就不必再次修改链接标记,用户将始终看到最新的CSS.浏览器将能够缓存CSS文件,但是当您对CSS进行任何更改时,浏览器会将此视为新URL,因此它不会使用缓存副本.
这也适用于图像,favicon和JavaScript.基本上任何不动态生成的东西.
kep*_*aro 181
简单的客户端技术
一般来说,缓存很好..所以有几种技术,取决于你在开发网站时是否自己解决问题,或者你是否试图在生产环境中控制缓存.
您网站的一般访问者将无法获得您在开发网站时所拥有的相同体验.由于普通访问者访问网站的频率较低(可能每月只有几次,除非您是Google或hi5网络),因此他们不太可能将您的文件放在缓存中,这可能就足够了.如果要在浏览器中强制使用新版本,可以随时向请求添加查询字符串,并在进行重大更改时提高版本号:
<script src="/myJavascript.js?version=4"></script>
Run Code Online (Sandbox Code Playgroud)
这将确保每个人都获得新文件.它的工作原理是浏览器查看文件的URL以确定它是否在缓存中有副本.如果您的服务器未设置为对查询字符串执行任何操作,则将忽略该服务器,但该名称将看起来像浏览器的新文件.
另一方面,如果您正在开发网站,则每次保存对开发版本的更改时都不希望更改版本号.那将是乏味的.
因此,在开发网站时,一个好方法是自动生成查询字符串参数:
<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>
Run Code Online (Sandbox Code Playgroud)
向请求添加查询字符串是对资源进行版本化的好方法,但对于简单的网站,这可能是不必要的.请记住,缓存是一件好事.
值得注意的是,浏览器并不一定吝啬将文件保存在缓存中.浏览器有针对此类事情的策略,它们通常按照HTTP规范中规定的规则进行播放.当浏览器向服务器发出请求时,响应的一部分是EXPIRES标头..一个告诉浏览器应该在缓存中保留多长时间的日期.下次浏览器遇到对同一文件的请求时,它会看到它在缓存中有一个副本,并查看EXPIRES日期以决定是否应该使用它.
不管你信不信,它实际上是你的服务器使浏览器缓存如此持久.您可以调整服务器设置并更改EXPIRES标头,但我上面写的小技巧可能是一种更简单的方法.由于缓存很好,您通常希望将该日期设置为远期("Far-future Expires Header"),并使用上述技术强制进行更改.
如果您对有关HTTP的更多信息或如何提出这些请求感兴趣,那么一本好书就是Steve Souders的"高性能网站".这是对这个主题的一个非常好的介绍.
Leo*_*opd 111
谷歌的apache mod_pagespeed插件会为你做自动版本控制.这真的很光滑.
它解析HTML从Web服务器出来(使用PHP,rails,python,静态HTML - 任何东西),并重写CSS,JS,图像文件的链接,因此它们包含一个id代码.它在修改后的URL上提供文件,并对它们进行非常长的缓存控制.文件更改后,它会自动更改URL,以便浏览器重新获取它们.它基本上只是工作,没有任何代码更改.它甚至会在出路时缩小你的代码.
lev*_*vik 92
我建议您使用实际CSS文件的MD5哈希,而不是手动更改版本.
所以你的网址会是这样的
http://mysite.com/css/[md5_hash_here]/style.css
Run Code Online (Sandbox Code Playgroud)
您仍然可以使用重写规则去除散列,但优点是现在您可以将缓存策略设置为"永久缓存",因为如果URL相同,则表示文件未更改.
然后,您可以编写一个简单的shell脚本来计算文件的哈希并更新您的标记(您可能希望将其移动到单独的文件中以便包含).
每次CSS更改时,只需运行该脚本即可.浏览器只会在更改文件时重新加载.如果您进行编辑然后撤消它,那么确定您需要返回哪个版本以便访问者不要重新下载是没有意义的.
Pha*_*007 63
不确定为什么你们为实施这个解决方案付出了太多的痛苦.
如果获取文件的修改时间戳并将其作为查询字符串附加到文件,则需要执行的操作
在PHP中,我会这样做:
<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">
Run Code Online (Sandbox Code Playgroud)
filemtime是一个返回文件修改时间戳的PHP函数.
SCd*_*CdF 51
您可以放在?foo=1234
css/js导入的末尾,将1234更改为您喜欢的任何内容.看一下SO html源代码示例.
这个想法有吗?无论如何,请求都会丢弃/忽略参数,您可以在推出新版本时更改该数字.
注意:关于它如何影响缓存有一些争论.我相信它的一般要点是有或没有参数的GET请求应该是可缓存的,因此上述解决方案应该可行.
但是,由Web服务器决定是否要遵守规范的那一部分以及用户使用的浏览器,因为它可以直接进入并要求提供新版本.
Joh*_*kin 40
我听说这个叫做"自动版本控制".最常见的方法是在URL中的某处包含静态文件的mtime,并使用重写处理程序或URL confs将其删除:
也可以看看:
Mic*_*pat 24
对于大约2008年的网站,30个左右的现有答案是很好的建议.然而,当涉及到现代的单页面应用程序(SPA)时,可能是时候重新思考一些基本假设了......特别是Web服务器只需要提供单个最新版本的服务的想法文件.
想象一下,您是一个在浏览器中加载了SPA 版本M的用户:
/some.template
/some.template
- 您是否希望它返回模板的版本M或N?如果/some.template
版本M和N之间的格式发生了变化(或文件被重命名或其他),您可能不希望将模板的版本N发送到运行解析器的旧版本M的浏览器.†
满足两个条件时,Web应用程序会遇到此问题:
一旦您的应用程序需要并行提供多个版本,解决缓存和"重新加载"变得微不足道:
/v<release_tag_1>/…files…
,/v<release_tag_2>/…files…
<script>
和<link>
标签等,以指向其中一个版本化目录中的该文件最后一步听起来很棘手,因为它可能需要为服务器端或客户端代码中的每个URL调用URL构建器.或者你可以巧妙地使用<base>
标签并在一个地方更改当前版本.
†解决这个问题的一种方法是积极地强制浏览器在发布新版本时重新加载所有内容.但是为了让任何正在进行的操作完成,可能仍然最容易并行支持至少两个版本:v-current和v-previous.
小智 14
不要使用foo.css?version = 1!浏览器不应该使用GET变量缓存URL.根据http://www.thinkvitamin.com/features/webapps/serving-javascript-fast,虽然IE和Firefox忽略了这一点,但Opera和Safari却没有!而是使用foo.v1234.css,并使用重写规则去除版本号.
Nic*_*son 10
RewriteRule需要对js或css文件进行小的更新,这些文件最后包含点符号版本控制.例如json-1.3.js.
我在正则表达式中添加了一个点否定类[^.],所以.number.被忽略了.
RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Run Code Online (Sandbox Code Playgroud)
use*_*893 10
对于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);
}
})();
Run Code Online (Sandbox Code Playgroud)
以上内容将查找用户上次访问您网站的时间.如果上次访问是在您发布新代码之前,则使用它location.reload(true)
来强制从服务器刷新页面.
我通常将此作为第一个脚本,<head>
因此在任何其他内容加载之前进行评估.如果需要重新加载,则用户几乎不会注意到.
我使用本地存储将最后一次访问时间戳存储在浏览器上,但是如果您希望支持旧版本的IE,则可以添加cookie.
在Laravel(PHP)中,我们可以按照清晰优雅的方式(使用文件修改时间戳)来完成:
<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>
Run Code Online (Sandbox Code Playgroud)
和CSS类似
<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">
Run Code Online (Sandbox Code Playgroud)
有趣的帖子.阅读完这里的所有答案,结合我从未遇到过"伪造"查询字符串的任何问题(我不确定为什么每个人都不愿意使用这个)我猜这个解决方案(不需要apache重写规则)如在接受的答案中)是计算CSS文件内容的短HASH(而不是文件日期时间)作为伪造的查询字符串.
这将导致以下结果:
<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />
Run Code Online (Sandbox Code Playgroud)
当然,日期时间解决方案也可以在编辑CSS文件的情况下完成工作,但我认为它是关于css文件内容而不是文件日期时间,那么为什么要将这些混合起来?
对于我的开发,我发现chrome有很好的解决方案。
https://developer.chrome.com/devtools/docs/tips-and-tricks#hard-reload
在开发人员工具打开的情况下,只需长按刷新按钮,然后将鼠标悬停在“空缓存和硬重载”上就可以释放。
这是我最好的朋友,并且是获得您想要的东西的超轻量级方法!
我最近使用 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)
Run Code Online (Sandbox Code Playgroud)
此代码基本上将文件时间戳作为查询参数附加到 URL。下面函数的调用
script("/main.css")
Run Code Online (Sandbox Code Playgroud)
会导致
<link rel="stylesheet" type="text/css" href="/main.css?1221842734">
Run Code Online (Sandbox Code Playgroud)
好处当然是你永远不必再次更改你的 HTML 内容,触摸 CSS 文件会自动触发缓存失效。它工作得很好,开销并不明显。
感谢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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
干杯谢谢.
小智 6
尚未找到客户端DOM方法动态创建脚本节点(或css)元素:
<script>
var node = document.createElement("script");
node.type = "text/javascript";
node.src = 'test.js?'+Math.floor(Math.random()*999999999);
document.getElementsByTagName("head")[0].appendChild(node);
</script>
Run Code Online (Sandbox Code Playgroud)
如果将session-id添加为js/css文件的spureous参数,则可以强制执行"会话范围的缓存":
<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>
Run Code Online (Sandbox Code Playgroud)
如果您想要版本范围的缓存,可以添加一些代码来打印文件日期或类似内容.如果您使用的是Java,则可以使用自定义标记以优雅的方式生成链接.
<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
Run Code Online (Sandbox Code Playgroud)
假设您有一个文件可用于:
/styles/screen.css
Run Code Online (Sandbox Code Playgroud)
您可以将带有版本信息的查询参数附加到URI上,例如:
/styles/screen.css?v=1234
Run Code Online (Sandbox Code Playgroud)
或者您可以添加版本信息,例如:
/v/1234/styles/screen.css
Run Code Online (Sandbox Code Playgroud)
恕我直言,第二种方法更适合CSS文件,因为它们可以使用相对URL引用图像,这意味着如果你指定background-image
如此:
body {
background-image: url('images/happy.gif');
}
Run Code Online (Sandbox Code Playgroud)
它的URL实际上是:
/v/1234/styles/images/happy.gif
Run Code Online (Sandbox Code Playgroud)
这意味着如果更新使用的版本号,服务器会将其视为新资源,而不是使用缓存版本.如果您的版本号基于Subversion/CVS/etc.修订这意味着将注意到对CSS文件中引用的图像的更改.不与第一方案保证,即URL images/happy.gif
相对/styles/screen.css?v=1235
是/styles/images/happy.gif
不包含任何版本信息.
我已经使用Java servlet使用这种技术实现了一个缓存解决方案,并简单地处理/v/*
委托给底层资源(即/styles/screen.css
)的servlet的请求.在开发模式中,我设置了缓存标头,告诉客户端始终使用服务器检查资源的新鲜度(在部署模式下,如果您委托给Tomcat,DefaultServlet
并且等文件没有更改,这通常会导致304 )我设置标题"永远缓存"..css
.js
你可以简单地用CSS/JS url添加一些随机数
example.css?randomNo=Math.random()
Run Code Online (Sandbox Code Playgroud)
对于ASP.NET,我认为下一个解决方案具有高级选项(调试/发布模式,版本):
通过这种方式包含的Js或Css文件:
<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />
Run Code Online (Sandbox Code Playgroud)
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();
}
...
}
Run Code Online (Sandbox Code Playgroud)
如果您使用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;
Run Code Online (Sandbox Code Playgroud)
小智 5
只需在您想要执行硬重新加载的位置添加此代码(强制浏览器重新加载缓存的 CSS 和 JavaScript 文件):
$(window).load(function() {
location.reload(true);
});
Run Code Online (Sandbox Code Playgroud)
在 内执行此操作.load
,这样它就不会像循环一样刷新。
对于开发:使用浏览器设置:例如,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>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
379296 次 |
最近记录: |