我注意到一些浏览器(特别是Firefox和Opera)非常热衷于使用.css和.js文件的缓存副本,甚至在浏览器会话之间。当您更新其中一个文件时,这会导致一个问题,但用户的浏览器会继续使用缓存的副本。
当文件发生更改时,强迫用户浏览器重新加载文件的最优雅的方法是什么?
理想情况下,该解决方案不会强制浏览器在每次访问页面时重新加载文件。
我发现John Millikin和da5id的建议很有用。这有一个专门的术语:自动版本控制。
我在下面发布了一个新的答案,这是我最初的解决方案和约翰的建议的结合。
SCdF建议的另一个想法是将伪查询字符串附加到文件中。(一些自动使用时间戳作为伪查询字符串的Python代码是由pi..提交的)
然而,关于浏览器是否缓存带有查询字符串的文件还存在一些讨论。(请记住,我们希望浏览器缓存该文件并在以后的访问中使用它。我们只希望它在文件更改时再次获取该文件。)
只需使用服务器端代码添加文件的日期…这样,它将被缓存,只有当文件更改时才会重新加载。
在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;
}
}
假设你有一个文件可用:
/styles/screen.css
你可以在URI上附加一个包含版本信息的查询参数,例如:
/styles/screen.css?v=1234
或者你可以在前面加上版本信息,例如:
/v/1234/styles/screen.css
恕我直言,第二种方法更适合CSS文件,因为它们可以使用相对url引用图像,这意味着如果你指定一个背景图像,像这样:
body {
background-image: url('images/happy.gif');
}
它的URL实际上是:
/v/1234/styles/images/happy.gif
这意味着如果您更新了使用的版本号,服务器将将其视为新资源,而不是使用缓存的版本。如果您的版本号基于Subversion、CVS等版本,这意味着CSS文件中引用的图像的更改将被注意到。第一种方案并不能保证这一点,即URL images/happy.gif相对于/styles/screen.css?V =1235是/styles/images/happy.gif,它不包含任何版本信息。
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".
我在为我的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");
}
下面是我基于Bash脚本的缓存破坏解决方案:
我假设在index.html文件中引用了CSS和JavaScript文件
在index.html中为.js和.css添加时间戳作为参数,如下所示(仅限一次)
用上面的时间戳创建一个timestamp.txt文件。
在对.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用户)你可以把这个脚本放在一个钩子中,这样每次文件保存在你的工作空间时它都会被调用。