发布于 2026-01-06 1 阅读
0

使用 Rails 和 Webpacker 实现关键 CSS - SprocketsLess 第一部分

使用 Rails 和 Webpacker 实现关键 CSS - SprocketsLess 第一部分

这是我们计划推出的一系列文章的第一篇,旨在介绍将所有资源迁移到 Webpacker 后可实现的全新高级用法。在第一部分中,我们将探讨如何优化 CSS 文件大小。

我们都希望网页快速稳定。在进行页面速度审核时,经常会建议使用关键 CSS。关键 CSS,尤其是首屏关键 CSS,是指将渲染页面顶部(首屏以上)所需的最小 CSS 内联到 HTML 中。我一直在寻找一种在 Rails 应用中实现这一目标的简便方法,但始终未能成功。

Webpack(即 Rails 中的 Webpacker)的一大优势在于其周边丰富的生态系统。虽然 Rails 的 JS 部分文档齐全,但 CSS 和图像处理方面也有很多工具可供使用。

几个月前,我发现GoRails发布了一个很棒的视频,讲解如何在 Rails 应用程序中使用PurgeCss

理解 PurgeCSS

替代文字

PurgeCSS 的核心理念是,一方面,你需要将所有可能使用 CSS 类(通常是 `<class>` .htmlhtml.erb`<style>`、`<style> .js`)的文件提供给 PurgeCSS。PurgeCSS 会创建一个列表,列出所有可能作为 CSS 选择器的标记。

另一方面,Webpacker 使用mini-css-extract-plugin.PurgeCSS 创建一个 CSS 包,并提取一个标记列表。

结果是这两个标记列表的交集。

多包多规则

使用 Webpacker 可以轻松创建多个打包文件。您只需some-pack.js在目录中创建一个新文件即可app/javascript/packs

我们总体上要做的事情是:

  • 定义第二个包,critical.js其中只包含一些 CSS 导入。
  • 将我们的 PurgeCSS 流程拆分到 PostCSS 中,以便应用更严格的规则critical.css
  • Dev.to 顺便说一下,请将关键 CSS 内联到 HTML 中
  • 懒加载我们的主程序application.css

我们的关键.js入口点

假设有一个application.js 文件,内容大致如下:

// app/javascript/packs/application.js

require("@rails/ujs").start();
require("local-time").start();
require("turbolinks").start();

window.Rails = Rails;

// import CSS
import "stylesheets/application.scss";

// import Stimulus controllers
import "controllers/index";

// import vendor JS
import "bootstrap";

我们的主要入口点是导入我们的主应用程序文件 application.scss,它通常看起来像这样:

// app/javascript/stylesheets/application.scss

// Fonts
@import "config/fonts";

// Graphical variables
@import "config/colors";

// Vendor
@import "~bootstrap/scss/functions";
@import "config/bootstrap_variables";
@import "~bootstrap/scss/bootstrap";

$fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "~@fortawesome/fontawesome-free/scss/fontawesome";
@import "~@fortawesome/fontawesome-free/scss/brands";

// Components
@import "components/index";

// layouts
@import "layouts/sticky-footer";

我们可以创建一个非常基础的程序critical.js,它唯一的功能就是导入一个新的 critical.scss 样式表。

// app/javascript/packs/critical.js
import "stylesheets/critical.scss";

在我们的critical.scss文件中,我们可以更有选择性地添加一些内容,以帮助 PurgeCSS 更好地完成工作。(这确实会带来一些细微的差别)

// colors
@import "config/colors";

// vendor
@import "~bootstrap/scss/functions";
@import "config/bootstrap_variables";
@import "config/bootstrap_critical"; // pick only the Bootstrap module you need

// Components
@import "components/banner"; //just pick the components you need for the homepage

PostCSS / PurgeCSS 配置

接下来是关键部分。我们需要告诉 PurgeCSS 对每个文件应用不同的规则。幸运的是,PostCSS 中包含了大量相关信息。

因此,我们可以将信息上下文传递给环境:

module.exports = ctx => environment(ctx);

向我们的环境变量添加一个上下文变量

const environment = ctx => ({
  plugins: [
    require("postcss-import"),
    require("postcss-flexbugs-fixes"),
    require("postcss-preset-env")({
      autoprefixer: {
        flexbox: "no-2009"
      },
      stage: 3
    }),
    purgeCss(ctx)
  ]
});

使用此上下文调用我们的 PurgeCss 插件

const purgeCss = ({ file }) => {
  return require("@fullhuman/postcss-purgecss")({
    content: htmlFilePatterns(file.basename),
    defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
  });
};

现在我们已经在 PurgeCss 中获取了文件名,就可以为每个文件指定不同的规则了。对于关键 CSS 文件,我只指定与首页相关的页面,而其他所有文件则使用常规的 CSS 模式。

const htmlFilePatterns = filename => {
  switch (filename) {
    case "critical.scss":
      return [
        "./app/views/pages/index.html.erb",
        "./app/views/shared/_navbar.html.erb",
        "./app/views/layouts/application.html.erb"
      ];
    default:
      return [
        "./app/**/*.html.erb",
        "./config/initializers/simple_form_bootstrap.rb",
        "./app/helpers/**/*.rb",
        "./app/javascript/**/*.js"
      ];
  }
};

所以完整起来,看起来是这样的。

// postcss.config.js

const environment = ctx => ({
  plugins: [
    require("postcss-import"),
    require("postcss-flexbugs-fixes"),
    require("postcss-preset-env")({
      autoprefixer: {
        flexbox: "no-2009"
      },
      stage: 3
    }),
    purgeCss(ctx)
  ]
});

const purgeCss = ({ file }) => {
  return require("@fullhuman/postcss-purgecss")({
    content: htmlFilePatterns(file.basename),
    defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [],
  });
};

const htmlFilePatterns = filename => {
  switch (filename) {
    case "critical.scss":
      return [
        "./app/views/pages/index.html.erb",
        "./app/views/shared/_navbar.html.erb",
        "./app/views/layouts/application.html.erb"
      ];
    default:
      return [
        "./app/**/*.html.erb",
        "./config/initializers/simple_form_bootstrap.rb",
        "./app/helpers/**/*.rb",
        "./app/javascript/**/*.js"
      ];
  }
};

module.exports = ctx => environment(ctx);

结果

我做了一个小测试,结果就是这样。

  • 初始包大小为 32kb
  • 清除 CSS 后,文件大小降至 9kb。
  • 我的 critical.css 文件只有 3kb!

嗯嗯嗯🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉

来自 Webpacker 的内联 CSS

我费了好大劲才把 CSS 文件内联到 HTML 里。多亏了Stack Overflow,我在这里得到了一些帮助。

<% if current_page?(root_path) %>
  <!-- Inline the critical CSS -->
  <style>
    <%= File.read(File.join(Rails.root, 'public', Webpacker.manifest.lookup('critical.css'))).html_safe %>
  </style>

  <!-- Lazy load the rest with loadCSS -->
  <link rel="preload" href="<%= Webpacker.manifest.lookup('application.css') %>" as="style" onload="this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="<%= Webpacker.manifest.lookup('application.css') %>"></noscript>
<% else %>
  <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<% end %>

瞧!

演示:https://sprockets-less-rails6.herokuapp.com
源代码:https://github.com/adrienpoly/sprockets-less-rails6

文章来源:https://dev.to/adrienpoly/ritic-css-with-rails-and-webpacker-sprocketsless-part-1-2bck