重构 CSS:优化大小和性能(第 3 部分)

已发表: 2022-03-10
快速总结 ↬重构的代码库应该会带来相似或改进的性能以及代码库的健康状况。 毕竟,如果部署重构的代码库导致加载或性能问题,则会导致流量和收入减少。 幸运的是,我们可以应用许多优化技术来解决潜在的文件大小和性能问题。

在本系列的前几篇文章中,我们介绍了审核 CSS 代码库运行状况以及增量 CSS 重构策略、测试和维护。 无论在重构过程中 CSS 代码库改进了多少,以及它的可维护性和可扩展性如何,最终的样式表都需要优化以获得最佳性能和最小文件大小。

部署重构的代码库不应导致更差的网站性能和更差的用户体验。 毕竟,用户不会永远等待网站加载。 此外,尽管代码质量有所提高,但管理层将对未优化的代码库导致的流量和收入下降感到不满。

在本文中,我们将介绍可以优化 CSS 文件大小、加载时间和渲染性能的CSS 优化策略。 这样,重构后的 CSS 代码库不仅更易于维护和扩展,而且性能更高,并且可以选中所有对最终用户和管理人员都很重要的框。

部分:CSS 重构

  • 第 1 部分:CSS 重构:简介
  • 第 2 部分:CSS 策略、回归测试和维护
  • 第 3 部分:优化大小和性能
  • 订阅我们的电子邮件通讯,不要错过下一个。

优化样式表文件大小

优化文件大小归结为删除不必要的字符和格式化并优化 CSS 代码以使用不同的语法或速记属性来减少文件中的字符总数。

优化和缩小

CSS 优化和缩小已经存在多年,并成为前端优化的主要内容。 在 CSS 优化和缩小方面,cssnano 和 clean-css 等工具是我最喜欢的工具之一。 它们提供了多种自定义选项,以进一步控制如何优化代码以及支持哪些浏览器。

这些工具以类似的方式工作。 首先,未优化的代码按照配置中设置的规则进行解析和转译。 结果是使用较少字符但仍保留格式(换行符和空格)的代码。

 /* Before - original and unoptimized code */ .container { padding: 24px 16px 24px 16px; background: #222222; } /* After - optimized code with formatting */ .container { padding: 24px 16px; background: #222; }

最后,通过删除所有不必要的文本格式来缩小转译的优化代码。 根据配置中设置的代码库和支持的浏览器,带有不推荐使用的供应商前缀的代码也可以被删除。

 /* Before - optimized code with formatting */ .container { padding: 24px 16px; background: #222; } /* After - optimized and minified code */ .container{padding:24px 16px;background:#222}

即使在这个基本示例中,我们也设法将整体文件大小从 76 字节减少到 55 字节,从而减少了 23%。 根据代码库和优化工具和配置,CSS 优化和缩小可能会更加有效。

由于只需对 CSS 工作流程进行一些调整即可获得巨大的回报,因此 CSS 优化和缩小可以被认为是轻松的胜利。 这就是为什么缩小应该被视为最低限度的性能优化和项目中所有样式表的要求。

跳跃后更多! 继续往下看↓

优化媒体查询

当我们用 CSS 编写媒体查询时,尤其是在使用多个文件(PostCSS 或 Sass)时,我们通常不会将代码嵌套在整个项目的单个媒体查询下。 为了提高可维护性、模块化和代码结构,我们通常为多个 CSS 组件编写相同的媒体查询表达式。

让我们考虑以下未优化 CSS 代码库的示例。

 .page { display: grid; grid-gap: 16px; } @media (min-width: 768px) { .page { grid-template-columns: 268px auto; grid-gap: 24px; } } /* ... */ .products-grid { display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 16px; } @media (min-width: 768px) { .products-grid { grid-template-columns: repeat(3, 1fr); grid-gap: 20px; } }

如您所见,我们为每个组件重复了@media (min-width: 768px) ,以提高可读性和维护性。 让我们在这个代码示例上运行优化和缩小,看看我们得到了什么。

 .page{display:grid;grid-gap:16px}@media (min-width: 768px){.page{grid-template-columns:268px auto;grid-gap:24px}}.products-grid{display:grid;grid-template-columns:repeat(2,1fr);grid-gap:16px}@media (min-width: 768px){.products-grid{grid-template-columns:repeat(3,1fr);grid-gap:20px}}

这可能有点难以阅读,但我们只需要注意重复的@media (min-width: 768px)媒体查询。 我们已经得出结论,我们想要减少样式表中的字符数,并且可以在单个媒体查询下嵌套多个选择器,那么为什么压缩器不删除重复的表达式呢? 原因很简单。

CSS 中的规则顺序很重要,因此要合并重复的媒​​体查询,需要移动代码块。 这将导致规则顺序被更改,这可能会导致样式中出现不必要的副作用。

但是,结合媒体查询可能会使文件大小更小,具体取决于代码库和结构。 postcss-sort-media-queries 等工具和包允许我们删除重复的媒体查询并进一步减小文件大小。

当然,有一个重要的警告是要有一个结构良好的 CSS 代码库结构,它不依赖于规则顺序。 在规划 CSS 重构和建立基本规则时,应该考虑到这种优化。

我建议首先检查优化收益是否超过潜在风险。 这可以通过运行 CSS 审核和检查媒体查询统计信息轻松完成。 如果是这样,我建议稍后添加它并运行自动回归测试以捕捉任何意外的副作用和可能因此发生的错误。

删除未使用的 CSS

在重构过程中,您总是有可能最终得到一些尚未完全删除的未使用的遗留样式,或者您将有一些未使用的新添加样式。 这些样式还增加了总字符数和文件大小。 然而,使用自动化工具消除这些未使用的样式可能会有些风险,因为这些工具无法准确预测实际使用的样式。

像 purgecss 这样的工具会遍历项目中的所有文件,并将文件中提到的所有类用作选择器,只是为了谨慎起见,不要意外删除动态、注入 JavaScript 元素的选择器以及其他潜在情况。 但是,purgecss 提供了灵活的配置选项作为这些潜在问题和风险的解决方法。

但是,只有在潜在收益大于风险时才应该进行这种改进。 此外,这种优化技术将需要相当长的时间来设置、配置和测试,并且可能会导致意想不到的问题,因此请谨慎操作并确保设置是防弹的。

消除渲染阻塞 CSS

默认情况下,CSS 是一种阻止渲染的资源,这意味着在浏览器下载并解析所有链接的样式表及其依赖项(例如字体)之前,网站不会显示给用户。

具有字体样式表和字体文件依赖性的渲染阻止 CSS 示例
具有字体样式表和字体文件依赖项的渲染阻止 CSS 示例。 (来自知识共享署名 4.0 许可下的 web.dev)(大预览)

如果样式表文件具有较大的文件大小或位于第三方服务器或 CDN 上的多个依赖项,则网站渲染可能会因网络速度和可靠性而显着延迟。

在过去的几个月中,最大内容涂料(LCP) 已成为一项重要指标。 LCP 不仅对性能很重要,而且对 SEO 也很重要——LCP 得分越高的网站搜索结果排名也会越好。 删除像 CSS 这样的渲染阻塞资源是提高 LCP 分数的一种方法。

但是,如果我们推迟样式表的加载和处理,这将导致Flash Of Unstyled Content (FOUC)——内容会立即显示给用户,并且稍后会加载和应用样式。 这个开关可能看起来很刺耳,甚至可能会让一些用户感到困惑。

关键 CSS

使用关键 CSS,我们可以确保网站以最少的样式加载,这些样式保证在最初呈现时在页面上使用。 这样,我们可以使 FOUC 变得不那么引人注目,甚至在大多数情况下消除它。 例如,如果主页具有带导航的标题组件和位于首屏的英雄组件,这意味着关键 CSS 将包含这些组件的所有必要的全局和组件样式,而页面上其他组件的样式将被推迟。

该 CSS 在 HTML 的style标记下内联,因此样式与 HTML 文件一起加载和解析。 虽然这会导致HTML 文件大小稍大(也应该缩小),但所有其他非关键 CSS 将被延迟并且不会立即加载,并且网站将呈现更快。 总而言之,好处超过了 HTML 文件大小的增加。

 <head> <style type="text/css"><!-- Minified Critical CSS markup --></style> </head>

根据您的设置,有许多自动化工具和 NPM 包可以提取关键 CSS 并生成延迟样式表。

延迟样式表

我们究竟如何使 CSS 成为非阻塞的? 我们知道,在第一次下载页面 HTML 时,不应在 HTML head元素中引用它。 Demian Renzulli 在他的文章中概述了这种方法。

目前还没有原生 HTML 方法来优化或延迟渲染阻塞资源的加载,因此我们需要在初始渲染后使用 JavaScript 将非关键样式表插入 HTML 标记中。 如果用户在浏览器中未启用 JavaScript 访问页面,我们还需要确保这些样式以非最佳(渲染阻止)方式加载。

 <!-- Deferred stylesheet --> <link rel="preload" as="style" href="path/to/stylesheet.css" onload="this.onload=null;this.rel='stylesheet'"> <!-- Fallback --> <noscript> <link rel="stylesheet" href="path/to/stylesheet.css"> </noscript>

使用link rel="preload" as="style"确保样式表文件被异步请求,而onload JavaScript 处理程序确保文件在 HTML 文档完成加载后由浏览器加载和处理。 需要进行一些清理,因此我们需要将onload设置为null以避免此函数多次运行并导致不必要的重新渲染。

这正是 Smashing Magazine 处理其样式表的方式。 每个模板(主页、文章类别、文章页面等)都有一个模板特定的关键 CSS内联在head元素的 HTML style标记内,以及一个包含所有非关键样式的延迟main.css样式表

然而,在这里我们可以看到媒体查询从自动延迟的低优先级print媒体切换到高优先级的all属性,而不是切换rel参数,当页面完成加载时。 这是延迟加载非关键样式表的另一种同样可行的方法。

 <link href="/css/main.css" media="print" onload="this.media='all'" rel="stylesheet">

使用媒体查询拆分和有条件地加载样式表

对于即使在应用上述优化后最终样式表文件仍具有较大文件大小的情况,您可以根据媒体查询将样式表拆分为多个文件,并在链接 HTML 元素中引用的样式表上使用 media 属性有条件地加载它们.

 <link href="print.css" rel="stylesheet" media="print"> <link href="mobile.css" rel="stylesheet" media="all"> <link href="tablet.css" rel="stylesheet" media="screen and (min-width: 768px)"> <link href="desktop.css" rel="stylesheet" media="screen and (min-width: 1366px)">

这样,如果使用移动优先的方法,则不会在可能运行在较慢或不可靠网络上的移动设备上下载或解析较大屏幕尺寸的样式。

重申一下,如果前面提到的优化方法的结果导致样式表的文件大小不理想,则应使用此方法。 对于常规情况,这种优化方法不会那么有效或有影响力,具体取决于单个样式表的大小。

推迟字体文件和样式表

延迟字体样式表(例如 Google 字体文件)也可能有利于初始渲染性能。 我们已经得出结论,样式表是渲染阻塞的,但样式表中引用的字体文件也是如此。 字体文件也会给初始渲染性能增加相当多的开销

加载字体样式表和字体文件是一个复杂的话题,深入研究它需要一篇全新的文章来解释所有可行的方法。 幸运的是,Zach Leatherman 在这本很棒的综合指南中概述了许多可行的策略,并总结了每种方法的优缺点。 如果您使用 Google 字体,Harry Roberts 概述了最快加载 Google 字体的策略。

如果您决定推迟字体样式表,您最终会得到 Flash of Unstyled Text (FOUT)。 该页面最初将使用备用字体呈现,直到下载并解析了延迟字体文件和样式表,此时将应用新样式。 这种变化可能非常明显,可能会导致布局变化并使用户感到困惑,具体取决于具体情况。

Barry Pollard 概述了一些可以帮助我们处理 FOUT 的策略,并谈到了即将推出的尺寸调整 CSS 功能,它将提供一种更简单、更原生的处理 FOUT 的方式。

服务器端优化

HTTP 压缩

除了缩小和文件大小优化之外,HTML、CSS 文件、JavaScript 文件等静态资源。Gzip 和 Brotli 等 HTTP 压缩算法可用于额外减小下载文件的大小。

需要在服务器上配置 HTTP 压缩,这取决于技术堆栈和配置。 但是,性能优势可能会有所不同,并且可能没有标准样式表缩小和优化那么大的影响,因为浏览器仍将解压缩压缩文件并必须解析它们。

缓存样式表

缓存静态文件是一种有用的优化策略。 浏览器仍然需要在第一次加载时从服务器下载静态文件,但是一旦它们被缓存,它们将在后续请求中直接从服务器加载,从而加快加载过程。

可以通过服务器级别的 Cache-Control HTTP 标头来控制缓存(例如,使用 Apache 服务器上的.htaccess文件)。

使用max-age我们可以指示文件应该在浏览器中缓存多长时间(以秒为单位),使用public ,我们指示文件可以被浏览器和任何其他缓存缓存。

 Cache-Control: public, max-age=604800

使用immutable配置可以实现更积极有效的静态资产缓存策略。 这告诉浏览器这个特定的文件永远不会改变,任何新的更新都将导致这个文件被删除,一个具有不同文件名的新文件将取代它。 这称为缓存清除

 Cache-Control: public, max-age=604800, immutable

如果没有适当的缓存清除策略,就有失去对缓存在用户浏览器上的文件的控制的风险。 这意味着如果文件要更改,浏览器将无法知道它应该下载更新的文件而不使用过时的缓存文件。 从那时起,我们几乎无法解决这个问题,用户将被过时的文件卡住,直到它过期。

对于样式表,这可能意味着如果我们用需要新样式的新内容和组件更新 HTML 文件,这些样式将不会显示,因为过时的样式表在没有缓存清除策略的情况下被缓存,浏览器不会知道这一点它必须下载新文件。

在对样式表或任何其他静态文件使用缓存策略之前,应实施有效的缓存清除机制以防止过时的静态文件卡在用户的缓存中。 您可以使用以下版本控制机制之一进行缓存清除:

  • 将查询字符串附加到文件名。
    例如styles.css?v=1.0.1. 但是,某些 CDN 可以完全忽略或从文件名中去除查询字符串,从而导致文件卡在用户的缓存中并且永远不会更新。
  • 更改文件名或附加哈希。
    例如styles.a1bc2.cssstyles.v1.0.1.css. 这比将查询字符串附加到文件名更可靠和有效。

CDN 还是自托管?

内容交付网络 (CDN) 是一组地理分布的服务器,通常用于可靠和快速地交付静态资产,如图像、视频、HTML 文件、CSS 文件、JavaScript 文件等。

尽管 CDN 似乎是自托管静态资产的绝佳替代品,但 Harry Roberts 对该主题进行了深入研究,并得出结论认为自托管资产对性能更有利。

“真的没有什么理由让你的静态资产留在其他人的基础设施上。 感知到的好处通常是一个神话,即使它们不是,取舍也根本不值得。 从多个来源加载资产的速度明显较慢。”

话虽如此,我建议默认情况下自托管样式表(包括字体样式表,如果可能),并且仅在有可行的理由或其他好处的情况下才迁移到 CDN。

审核 CSS 文件大小和性能

WebPageTest 和其他类似的性能审计工具可用于详细了解网站加载过程、文件大小、渲染阻止资源等。这些工具可以让您深入了解您的网站如何在各种设备上加载 -从在高速网络上运行的台式电脑到在慢速和不可靠网络上运行的低端智能手机。

让我们对本系列第一篇文章中提到的网站进行性能审计 - 具有 2MB 缩小 CSS 的网站。

首先,我们将查看内容细分以确定哪些资源占用的带宽最多。 从下面的图表中,我们可以看到图像占用了大部分请求,这意味着它们需要延迟加载。 从第二个图表中,我们可以看到样式表和 JavaScript 文件的文件大小是最大的。 这很好地表明这些文件需要被缩小和优化、重构或拆分为多个文件并异步加载。

两个图表显示按 MIME 类型划分的内容
按 MIME 类型划分的内容细分(在第一个视图上)。 (大预览)

我们可以从 Web Vitals 图表中得出更多结论。 通过查看最大内容绘制 (LCP) 图表,我们可以详细了解渲染阻塞资源以及它们对初始渲染的影响程度。

我们已经可以得出结论,网站样式表将对 LCP 和加载统计数据产生最大影响。 但是,我们可以看到样式表中引用的字体样式表、JavaScript 文件和图像,它们也是呈现阻塞的。 知道我们可以应用上述优化方法通过消除渲染阻塞资源来减少 LCP 时间。

最大的内容绘画图表
发生在 8561 毫秒的最大内容绘制图表。 请注意资源列表中时间轴上的橙色灯泡——这些资源正在阻塞渲染。 (大预览)

结论

当代码的健康和质量得到改善以及代码库的弱点和问题得到修复时,重构过程还没有完成。 与遗留代码库相比,重构的代码库应该产生相同或改进的性能

最终用户不应遇到重构代码库的性能问题或加载时间过长。 幸运的是,有许多方法可以确保代码库既健壮又高效——从简单的缩小和优化方法到更复杂的方法,如消除渲染阻塞资源和代码拆分。

我们可以使用WebPageTest等各种性能审计工具来详细了解加载时间、性能、渲染阻塞资源和其他因素,以便我们能够及早有效地解决这些问题。

部分:CSS 重构

  • 第 1 部分:CSS 重构:简介
  • 第 2 部分:CSS 重构:策略、回归测试和维护
  • 第 3 部分: CSS 重构:优化大小和性能
  • 订阅我们的电子邮件通讯,不要错过下一个。

参考

  • “渲染阻塞 CSS”,Ilya Grigorik
  • “推迟非关键 CSS”,Demian Renzulli
  • “字体加载策略综合指南”,Zach Leatherman
  • “减少字体加载影响的新方法:CSS 字体描述符”,Barry Pollard
  • “自行托管您的静态资产”,Harry Roberts
  • “优化 WebFont 加载和渲染”,Ilya Grigorik