从浏览器缓存 CSS 中可以获得重大性能提升。您可以确保您的 服务器已设置 以发送指示浏览器将 CSS 文件保留一定时间的标头。这是许多(如果不是大多数)网站已经在执行的最佳实践。
与浏览器缓存并存的是 _缓存清除_。假设浏览器将 CSS 文件缓存了一年(并不罕见)。然后您想更改 CSS。您需要一种策略来清除缓存并强制浏览器下载 CSS 的新副本。
以下是一些方法。
CSS 必须被缓存,这样才有效……
为了确保这一点,以下是一些看起来很健康的缓存 CSS 文件标头

我们要找的是 `Cache-Control` 和 `Expires` 标头。我不是服务器配置专家。我可能会看看 H5BP 服务器配置。但以下是一些经典的 Apache/HTAccess 方法来实现这一点
<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(\.gz)?$">
Header set Expires "Thu, 15 Apr 2020 20:00:00 GMT"
</FilesMatch>
<IfModule mod_expires.c>
ExpiresActive on
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
</IfModule>
查询字符串
如今大多数浏览器都会将带有不同查询字符串的 URL 视为不同的文件并下载新副本。大多数 CDN 甚至 支持并推荐 此操作。
<link rel="stylesheet" href="style.css?v=3.4.1">
进行微小的更改?将其更改为
<link rel="stylesheet" href="style.css?v=3.4.2">
您可以通过设置一个服务器端变量并在多个地方使用它来简化操作。因此,更改它将同时清除许多文件的缓存。
<?php $cssVersion = "3.4.2"; ?>
<link rel="stylesheet" href="global.css?v=<?php echo $cssVersion; ?>">
更改文件名
查询字符串并不总是有效。一些浏览器不会将不同的查询字符串视为不同的文件。有些软件(我听说:Squid)不会缓存带有查询字符串的文件。Steve Souders 告诉我们不要这样做。
一个类似的概念是更改文件名本身。例如,在 HTML 中
<link rel="stylesheet" href="style.232124.css">
您会以编程方式处理它,而不是在项目中真正更改文件名。由于该文件实际上不存在于服务器上,因此您需要执行一些技巧才能将其路由到正确的文件。Jeremy Keith 最近介绍了他的技术,它可以实现这种操作。
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+).(\d+).(js|css)$ $1.$3 [L]
这告诉服务器忽略 JavaScript 和 CSS 文件名中的这些数字,但只要我更新该数字,浏览器仍然会将其解释为新文件。
他使用 Twig,因此他使用的模板最终就像
{% set cssupdate = '20150310' %}
<link rel="stylesheet" href="/css/main.{{ cssupdate }}.css">
我相信您可以想象任何后端语言中都会有类似的版本(例如 ASP)。通过创建构建工具或部署脚本来更新变量本身,您可以更上一层楼。
将缓存清除“数字”基于文件更新日期
在搜索有关缓存清除的内容时,您会看到很多建议,建议您使用服务器检查文件最后更新的时间以创建缓存清除“数字”(数字,即用于清除缓存的任何内容)。
function autoversion($url) {
$path = pathinfo($url);
$ver = '.'.filemtime($_SERVER['DOCUMENT_ROOT'].$url).'.';
return $path['dirname'].'/'.str_replace('.', $ver, $path['basename']);
}
<link href="<?php autoversion('/path/to/theme.css'); ?>" rel="stylesheet">
我对此并不了解。在我看来,让服务器在每次页面浏览时都查找此信息会非常密集,在生产环境中很危险。过去我曾做过一些事情,比如“我只需要让 PHP 在数据属性中输出图片的尺寸!”结果却发现它把服务器搞得停滞不前。总之,小心为妙。
ETags
ETags 看起来是个好主意,因为它们的重点是检查浏览器是否已经拥有该文件的副本的信息。
但大多数建议都说:“关闭 ETags 标头”。雅虎 表示
ETags 的问题是它们通常使用使它们对托管站点的特定服务器唯一的属性构建。当浏览器从一台服务器获取原始组件,然后尝试在另一台服务器上验证该组件时,ETags 不会匹配,这种情况在使用服务器集群来处理请求的网站上太常见了。
另一个问题是它们不像实际缓存那样有效。为了检查 ETag,仍然需要进行网络请求 才能进行验证。影响性能的不仅仅是下载文件,还有所有网络协商和延迟问题。
同样,我不是专家,但以下是在 Apache 中通常建议关闭它们的方法
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None
框架会帮我们做
Rails 资产管道
我对 Rails 资产管道 和 Sprockets 有点经验。在我看来,这是一个非常理想的系统。我在模板中链接样式表
<%= stylesheet_link_tag "about/about" %>
它会生成类似以下内容的 HTML 代码
<link href="http://assets.codepen.io/assets/about/about-7ca9d3db0013f3ea9ba05b9dcda5ede0.css" media="screen" rel="stylesheet" type="text/css" />
只有当文件更改时,缓存清除数字才会更改,因此您只会清除需要清除的文件的缓存。此外,它还包含用于图像和 JavaScript 的方法。
WordPress
如果您在 WordPress 中使用 _页面_ 缓存工具,例如 W3 Total Cache 或类似工具,您可能就不必太担心 `filemtime` 业务过于占用服务器资源。
Gilbert Pellegrom 发布了一篇关于 WordPress 特定技术的文章,使用它
wp_register_style( 'screen', get_template_directory_uri().'/style.css', array(), filemtime( get_template_directory().'/style.css' ) );
wp_enqueue_style( 'screen' );
// Example Output: /style.css?ver=1384432580
WordPress 插件 Busted! 在后台做同样的事情。只是它会自动地对所有内容进行操作。
CodeKit
CodeKit 没有内置的方法来更改文件名,但它确实提供了一种方法可以在您设置的环境下执行 Shell 脚本。

Michael Russell 有一篇关于如何在文件中注入时间戳的博客文章,我相信您可以修改它来改为更改文件名。
构建工具
所有流行的任务运行器/构建工具都有插件可以帮助更改文件名。Sufian Rhazi 有一篇文章 介绍了如何在原生 Node.js 中执行此操作。
Grunt
Gulp
Broccoli
预处理器中
在将资产链接到其他资产时(例如,从 LESS 文件中链接的图像),可以使用预处理器来处理。 Ben Nadel 发表了一篇文章,介绍了如何使用这种方法。
异步 CSS
随着关键 CSS 的流行,延迟加载 CSS 变得越来越普遍。 还有其他一些延迟加载 CSS 的原因(例如,打印 CSS 或缓存预取)。
如果您使用 loadCSS(或者可能是 注入链接标签)加载 CSS,则需要在 JavaScript 本身中更新它所请求的文件名。 这与更改文件名不同,但并不完全不同。
所以
还有哪些我漏掉了? 您的缓存清除策略是什么?
我在一所大型大学(12000 多名学生)工作,我们在 .net 环境中使用“上次更新”方法。 我们在服务器“停止运行”方面没有任何问题。 我们在 4 个不同的文件上都启用了自动查询字符串。 它运行得非常完美,并且与我们团队的工作流程完美集成。 在 .net 中,我们还可以奢侈地缓存页面的不同部分或缓存控件。 我无法谈论其他语言,但对我们来说,它运行得很好。
Django 还可以通过配置 ManifestStaticFileStorage 来使用未来很长时间的到期标头。 它的工作原理类似于 Rails 的工作原理。 使用以下方式链接到您的文件
输出将类似于以下内容
“更改文件名”部分的重写规则是错误的。 应转义点,例如:
RewriteRule ^(.+)\.(d+)\.(js|css)$ $1.$3 [L]
,否则它们将匹配任何字符,而不仅仅是点。我认为 CSS 的语义版本控制并不是一个非常实用的想法。 跟踪哪些 CSS 是补丁,哪些是改进,哪些是重大更改,将非常困难,而且当您可以强制所有浏览器下载最新版本时,这样做完全没有意义。
同意!
我使用一个 PHP include,它可以进行缩小和缓存清除
当然,如果网站处于生产环境中,缩小的 CSS 将不再可写,脚本将简化为
htaccess 与 Stephano 所说的一样
当然,取决于 CMS,不需要执行所有这些操作 :)
检查 mtime 是一个非常快的操作,尤其是与让服务器打开、读取和解析图像文件以获取其像素尺寸相比。
我个人建议优化开发人员时间,让服务器检查 mtime。 如果任何性能不够好(这不会发生),您总是可以稍后进行优化。
这就是 W3 Total Cache 的工作原理。 Rails 似乎正在计算 MD5,这是一个更密集的操作(但也不太可能给 Web 服务器带来负担)。
^ 我认为这条评论非常中肯。
即使获取图像大小也会显著降低服务器速度,我会感到惊讶——假设我们谈论的是本地图像,而不是位于另一个网站/服务器上的图像(这些图像需要先下载!)。
文件访问不像您想象的那么重要。 请记住,典型的框架(例如 Rails)需要访问大量文件才能处理每个请求——这仅仅是为了启动框架。 在 PHP 中,您可以通过在页面中添加以下代码来查看有多少个文件被加载
<?php var_dump( count(get_included_files()) ) ?>
我刚在我的网站上检查了这一点,它使用的是 Laravel 4。 我在每个请求上加载了 200 个文件。
哦,因为数字很好……
查看 PHP get_image_size() 页面上的这条评论,有人进行了一些测试。 据称,检索 10,000 个文件的图像尺寸需要 0.15 秒。
该死,像我这样的无能之辈可以使用编辑按钮。 ;)
https://php.ac.cn/manual/en/function.getimagesize.php#94178
我也更喜欢 md5 方式,因为文件访问由文件系统层缓存,您也可以将其添加到 twig 中。 例如
gulp-rev 可能是最常见的用于此的 gulp 插件。
它会根据文件内容生成一个文件名,以及一个包含所有已更改名称及其更改后名称的 json 文件。
我对建议删除 ETags 有点困惑。 这篇文章由 Ilya Grigorik 撰写,建议添加它们,但 H5BP 似乎删除了它们
我想,如果您只使用一个服务器,则应添加 ETags,否则删除它们?
H5BP github 页面上的注释为您提供了一个线索。 如果您设置了未来很长时间的到期标头,则 ETags 毫无意义。
ETag 可以让服务器告诉浏览器文件自上次下载以来是否已修改。 如果您设置了(例如)一年或十年的到期标头,那么浏览器永远不会问这个问题——因为在该日期到来之前很久,该项目就会从浏览器缓存中清除,并且需要重新下载。
阻止浏览器询问这个问题更好,因为您减少了浏览器和服务器之间的 HTTP 通信。
当然,设置未来很长时间的到期“缺点”(如果可以这样说)是您必须处理缓存清除。
一如既往,很棒的文章,非常信息丰富且有用。 只是在“更改文件名”下的第二句话中注意到一个小的拼写错误,缺少一个“e” from different。
我们使用
gulp-rev
。我没有看到它在这里被提及,但它类似于 Rails、Laravel Elixir 等所做的。基本上,你使用
gulp-rev
根据内容重命名你的资源文件。请注意,这适用于 CSS 文件、JS 文件、图像文件,任何文件。一旦你的文件被重命名,你读取清单文件以将原始文件名映射到重命名的文件,并提供服务。这非常快,因为服务器正在提供一个普通的文件 - 没有重写,没有 PHP 逻辑,什么都没有。而且,如果你担心在每次页面加载时读取清单,你可以轻松地在 Redis 或 memcached 中缓存它,以便快速查找。
这里的另一个好处是,你不需要维护任何手动版本计数器。你只需要运行一个 gulp 任务,你就完成了。如果给定文件没有更改,它的文件名不会更改,因此缓存不会被破坏。
gulp-rev 可能是为此最常用的 gulp 插件。
这些策略是我用于我的 网站 的策略。
一些注意事项。
Squid 和查询字符串
你提到这是 Steve Souders 发现的。它与当时 Squid 的默认配置有关。该默认值在 Squid 2.7 中进行了更改,它是在 7 年前发布的。
我问 Steve 他是否仍然认为这是一个问题。回复:“不确定。难以测试。我不知道任何已知问题。”
虽然 7 年不能保证你不会遇到仍然运行着该默认配置的 Squid 代理,但我将查询字符串用于版本控制置于“相当安全”的类别。
Etags
这是另一个默认配置存在问题的问题,这次是在 Apache 中。它使用
FileETag INode MTime Size
。主要问题是使用 inode,它肯定与服务器之间不同。这在 2.4 中得到了修复,当时默认值切换到FileETag MTime Size
。由于这是 Web 服务器控制的内容,因此在大多数情况下,修复它很容易。
你说得对,这不应该用作缓存的替代方案,而应该(正确地)与缓存一起使用。
多年来尝试了 Chris 这里提到的所有方法后,没有一种能像使用其校验和“指纹”的资源的超长缓存时间那样好。这是 Rails/Sprockets 和 Ember 开箱即用的方法。
它是最一致的,并且只在资产更改时强制重新下载。
如果你是经常进行大量更改,这确实会对“资产周转”产生一定影响。在大多数情况下,这是一个可以接受的权衡。HTTP/2 将解决这个问题,并将彻底改变我们打包和提供资源的方式。
我厌倦了在 EE 模板中手动更新文件名,所以我制作了一个小型插件来帮我进行缓存清除:https://github.com/vfalconi/cache-override
示例
产生
真的很想看到更多这样的内容