使用 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>` .html、html.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
