模式库优先:一种管理 CSS 的方法
已发表: 2022-03-10在这篇文章中,基于我在多伦多 Smashing Conference 上的演讲,我将描述我在过去两年中采用的一种工作方法,它可以帮助我在我的项目中管理 CSS。
我将向您展示如何使用模式库工具 Fractal,逐个组件地管理您的 CSS,同时允许您使用您已经熟悉的工具。 虽然这是对分形的介绍,以及我们选择这个特定模式库的原因,但这种工作方式很可能会转移到其他解决方案。
我们的项目
我的公司有几个产品——Perch 和 Perch Runway CMS 和 Notist,一种面向公众演讲者的软件即服务应用程序。 这些产品完全不同,特别是考虑到 Perch 是自托管系统而 Notist 是 SaaS,但是它们都有很多用户界面需要开发。 我们还拥有这些产品的所有相关网站和文档,以及我们从事的其他工作,例如 24 Ways 网站。 在两年前发现 Fractal 之后,我们将每个新项目——无论大小——都转移到了 Fractal。
我们想要解决的问题
两年前,当我开始为版本 3 重建 Perch UI 时,我开始研究模式库解决方案。Perch 的一个特性是,您为网站上的内容输出创建的模板成为管理 UI 的架构。 这意味着模板中使用的任何字段类型都需要能够与任何其他字段类型一起存在。 我们不知道我们的客户如何组合这些,并且有大量可能的组合。 它也不是一个“网站”,我不想尝试将模式库强加到为组织网站模式而设计的东西中。
由于 Perch 是自托管的——人们下载它并将其托管在自己的服务器上——我们需要尽可能使用最简单的技术堆栈,以免在人们面前设置任何额外的进入障碍,其中许多人不熟悉使用CMS。 为了增加额外的乐趣,我们支持回到 Internet Explorer 9,但我打算使用大量的 Flexbox——因为这是在 Grid Layout 发布之前。
我也热衷于避免使用伴随着大量重新学习我们的工作方式并彻底改变我们的流程的工具。 任何额外的工具或对项目工作方式的改变都会带来新的摩擦。 你可以解决一个问题,但如果你对工作方式做出重大改变,就会带来一系列全新的问题。 在我们的例子中,我们以相当有限的方式使用 Sass,并使用 Gulp 进行处理。 我们的项目都没有使用 Javascript 框架,我们只是在编写 HTML、CSS 和 JavaScript。
分形完全符合我们的需求。 它与您的开发方式或您想要使用的工具无关。 对我们来说重要的是,它并没有假设我们正在建立一个网站。 这个实验非常成功,我们发现自己在每个大小项目中都使用了 Fractal,因为它使处理 CSS 的过程更加简单。 即使是我自己创建的小型网站也经常从 Fractal 开始,因为使用模式库的好处比你想象的要多,其中许多好处对于一个团队和一个大团队同样有意义.
在我们考虑如何使用 Fractal 进行开发以及为什么我认为它对小型项目和大型项目都有意义之前,让我们先看看如何设置环境。
分形入门
使用 Fractal 最直接的方法是访问 Fractal 网站并查看入门指南。 您首先需要全局安装 Fractal,然后可以按照此处列出的步骤创建一个新的 Fractal 项目。
安装好新项目后,在命令行切换到刚刚创建的文件夹并运行命令:
fractal start --sync
这将在端口 3000 启动一个小服务器,因此您应该能够在 Web 浏览器中访问https://localhost:3000
并查看您的项目。
现在您的项目已经启动并运行,在您最喜欢的文本编辑器中打开项目文件夹,并在components/example
下找到示例组件。 您将找到一个配置文件和一个名为example.hbs的文件。 example.hbs模板是您的组件的 HTML,您可以向其中添加更多 HTML,Fractal 将自动重新加载并显示它。 将文件更改为:
<h1>This is my heading</h1> <p>{{ text }}</p>
您应该会看到标题出现在浏览器中。 配置文件可用于添加内容或配置您的组件。 如果您想从该文件中读取标题文本,请将该文件编辑为如下示例:
title: Example component context: text: This is an example component! heading: My heading
现在更改您的example.hbs文件以读取该文本。
<h1>{{ heading }}</h1> <p>{{ text }}</p>
添加其他组件
您可以按照示例组件的模式添加您自己的。 至少,您需要一个文件夹(组件的名称)和一个使用相同名称的.hbs文件。 如果要设置配置选项,可以添加配置文件。
组件可以嵌套到文件夹中,以便更轻松地定位特定组件,而如何构建文件夹完全取决于您。
注意:很容易发现自己花费大量时间担心如何命名组件。 至少在 Fractal 中,重命名组件并将其重新组织到文件夹中很简单。 您可以重命名或移动它们,Fractal 将更新以显示新结构。 我发现理想的结构通常只会随着我的发展而变得明显,所以我一开始不用担心太多,然后再把它固定下来。
添加 CSS 工作流
到目前为止,我们能够创建 HTML 组件作为把手模板,以及一个用于插入数据的配置文件,但是,我们还没有添加任何 CSS。 理想情况下,我们希望将每个组件的 CSS 添加到与其余组件文件相同的文件夹中,然后将它们组合在一起。
我提到 Fractal 对您的工作流程做出的假设很少。 因此,与强迫您采用特定的工作方式相比,它的开箱即用功能要少得多。 然而,我们可以很容易地让 Fractal 与 Gulp 设置一起工作。
结合 Fractal、Sass 和 Gulp
下面描述了使用 Gulp 和 Sass 创建单个输出 CSS 文件的最小设置。 希望你可以按照这个过程做任何你在 Gulp 中通常会做的事情。 需要注意的关键是,其中大部分都不是 Fractal 特定的,所以一旦你让 Fractal 部分工作,你可以按照相同的模式添加其他任何东西。 如果您熟悉另一个构建工具,那么您很可能可以创建一个类似的过程; 如果你愿意,并且乐于分享,请在评论中告诉我们。
首先进行一些设置,以下将使您能够按照本教程中列出的代码进行操作,您的 Sass 文件和输出 CSS 的位置最终可能与我的不同。 关键是输出 CSS 文件需要位于公共文件夹中的某个位置。
- 在 Fractal 安装的公用文件夹中,添加一个名为css的文件夹。
- 在 Fractal 的根文件夹中,安装添加文件夹assets ,其中是文件夹scss 。 在该文件夹中创建一个名为global.scss的 Sass 文件。 在该文件中添加以下行:
@import "../../components/**/*.scss";
- 在
example
组件目录中创建一个名为example.scss的文件。 - 在 Fractal 项目的根目录中创建gulpfile.js并添加以下代码。
'use strict'; const gulp = require('gulp'); const fractal = require('./fractal.js'); const logger = fractal.cli.console; const sass = require('gulp-sass'); const sassGlob = require('gulp-sass-glob'); const plumber = require('gulp-plumber'); const notify = require('gulp-notify'); const path = require('path'); gulp.task('sass',function() { return gulp.src('assets/scss/**/*.scss') .pipe(customPlumber('Error running Sass')) .pipe(sassGlob()) .pipe(sass()) .pipe(gulp.dest('public/css')) }); gulp.task('watch', ['sass'], function() { gulp.watch([ 'components/**/*.scss', 'assets/scss/**/*.scss' ], ['sass']); }); function customPlumber(errTitle) { return plumber({ errorHandler: notify.onError({ title: errTitle || "Error running Gulp", message: "Error: <%= error.message %>", }) }); } gulp.task('fractal:start', function(){ const server = fractal.web.server({ sync: true }); server.on('error', err => logger.error(err.message)); return server.start().then(() => { logger.success(`Fractal server is now running at ${server.url}`); }); }); gulp.task('default', ['fractal:start', 'sass', 'watch']);
然后我安装文件顶部列出的依赖项。 如果您要在命令行安装它们,您将运行:
npm install gulp gulp-sass gulp-sass-glob gulp-plumber gulp-notify
sass
函数将资产中的 Sass 编译成单个文件,并将其输出到public
文件夹中。
gulp.task('sass',function() { return gulp.src('src/assets/scss/**/*.scss') .pipe(customPlumber('Error running Sass')) .pipe(sassGlob()) .pipe(sass()) .pipe(gulp.dest('public/css')) });
然后我创建了一个watch
函数,它将监视我的 Sass assets
以及单个组件,并将其编译到公共文件夹中。
gulp.task('watch', ['sass'], function() { gulp.watch([ 'components/**/*.scss', 'assets/scss/**/*.scss' ], ['sass']); });
那是我的CSS建筑。 我现在想做它,以便我可以运行 gulp,它会开始观察 CSS 文件以及开始分形。 我通过创建一个 gulp 任务来运行分形启动命令来做到这一点。
gulp.task('fractal:start', function(){ const server = fractal.web.server({ sync: true }); server.on('error', err => logger.error(err.message)); return server.start().then(() => { logger.success(Fractal server is now running at ${server.url}); }); });
最后,当我运行 gulp 和命令行时,我需要确保 Sass 构建和 Fractal 启动运行:
gulp.task('default', 'fractal:start', 'sass', 'watch');
那是我完成的gulpfile.js 。 如果您将其添加到您的默认 Fractal 项目中,请确保文件夹针对所提到的路径就位。 您应该能够转到命令行,运行gulp
,Fractal 将启动。
我们可以通过在global.scss文件中添加一个变量来测试我们的 Sass; 您需要将其添加到包含组件的行上方,以便变量可用于这些组件。
$color1: rebeccapurple;
然后在example.scss
中为我们之前添加的 1 级标题添加规则:
h1 { color: $color1; }
如果一切设置正确,您应该会发现 public/css 中有一个.css文件,其中包含以下规则:
h1 { color: rebeccapurple; }
我们还需要做一件事,以便我们可以使用我们正在构建的 CSS 预览我们的组件。 我们需要创建一个预览文件,它将链接到公共文件夹中的样式表。
在您的组件文件夹中,创建一个名为_preview.hbs的文件。
预览文件本质上是一个 HTML 文档,链接到我们的 CSS 和您需要包含的任何其他内容。 正文中有一个标签{{ yield }}
,这是放置组件的地方。
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Preview Layout</title> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="{{ path '/css/global.css' }}"> </head> <body> {{{ yield }}} </body> </html>
注意:公用文件夹还可以容纳您需要在组件中显示的任何其他资产,例如图像、字体等。
模式库作为真理的来源
正如我们所见,Fractal 可以构建我们的 CSS。 在我们的项目中,我们使 Fractal 成为我们为网站构建和处理 CSS 和其他资产的唯一地方。 这意味着我们的模式库和站点或应用程序不会漂移。 如果人们开始编辑站点的 CSS 并且没有将这些更改带回模式库,那么在您部署站点之后就会发生漂移。 如果您可以将模式库作为处理 CSS 的地方,那么更改必须从那里开始——这可以防止实时站点和库之间的漂移。
我们在 Fractal 中构建所有内容,然后将这些公共资产复制到要部署的实时站点。 除了防止系统之间的偏差之外,它还使源代码管理中的 CSS 管理变得更加容易。 当多人处理一个 CSS 文件时,合并冲突可能很难处理。 当人们在模式库中处理单个组件时,您通常可以避免两个人同时对同一个文件进行更改,如果他们这样做,则只需整理一个小文件而不是所有 CSS。
使用模式库优先方法来管理回退
我发现,工作模式库首先使处理代码中的回退比尝试一次修复完整的站点或应用程序更直接,也更不压倒性。 它还使我们能够专注于最好的情况,并在新技术上发挥创造力,而不是因为担心如何让它在不支持的浏览器中正常工作而限制我们所做的事情。
我们可以看一个简单的媒体对象组件案例,看看它是如何工作的。 接下来,在 Fractal 中的组件内创建一个媒体文件夹,并添加文件media.hbs和media.scss 。
从好的标记开始
您的起点应该始终是结构良好的标记。 在模式库中,您可能会将此组件与一系列标记一起使用,例如,您可以使用一个组件,其内容在一个位置标记为图形,而在另一个位置仅使用 div。 但是,您的内容应该以一种有意义的方式构建,并且可以从上到下阅读。
这可确保您的内容在非常基本的级别上可访问,但这也意味着您可以利用正常流程。 Normal Flow 是浏览器默认显示您的内容的方式,块元素在块维度中一个接一个地前进,而内联元素(例如句子中的单词)沿着内联轴运行。 对于许多正是您想要的内容,并且通过利用正常流程而不是与之抗争,您可以在创建布局时使您的工作变得更加容易。
因此,我的组件具有添加到media.hbs的以下标记。
<div class="media"> <div class="img"> <img src="/img/placeholder.jpg" alt="Placeholder"> </div> <h2 class="title">This is my title</h2> <div class="content"> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vehicula vitae ligula sit amet maximus. Nunc auctor neque ipsum, ac porttitor elit lobortis ac. Vivamus ultrices sodales tellus et aliquam. Pellentesque porta sit amet nulla vitae luctus. Praesent quis risus id dolor venenatis condimentum.</p> </div> <div class="footer"> An optional footer goes here. </div> </div>
你可以看到它是如何在 Fractal 中显示的:
一旦我得到了我想要的标记,我就会在我想到的桌面显示器上工作。 我将使用 CSS Grid Layout 和grid-template-areas
方法。 将以下内容添加到media.scss 。
img { max-width: 100%; } .media > .title { grid-area: title; } .media > .img { grid-area: img; } .media > .content { grid-area: bd; } .media > .footer { grid-area: ft; } .media { margin-bottom: 2em; display: grid; grid-column-gap: 20px; grid-template-columns: 200px 3fr; grid-template-areas: "img title" "img bd" "img ft"; }
我们现在有一个简单的媒体对象布局:
您可以在 Fractal 中做的事情是添加组件的变体。 您可能想要翻转媒体对象,使图像位于右侧。
现在将 CSS 添加到media.scss以翻转布局:
.media.media-flip { grid-template-columns: 3fr 200px ; grid-template-areas: "title img" "bd img" "ft img"; }
有两种创建变体的方法:基于文件和基于配置。 基于文件的最简单,如果您的变体具有不同的标记,它也很有用。 要创建基于文件的变体,请在名为 media --flip.hbs (即文件名中的两个破折号)的 media 文件夹中制作组件的副本。
该组件应该具有与添加到第一行的media-flip
类相同的标记,然后您将能够看到这两个版本。
<div class="media media-flip">
或者,在这种情况下,我们需要做的就是添加一个类,您可以使用配置文件创建一个变体。
如果您想这样做,请删除您的变体文件,然后添加一个名为media.config.json的配置文件,其中包含以下代码:
{ "title": "Media Object", "context": { "modifier": "default" }, "variants": [ { "name": "Flipped", "context": { "modifier": "flip" } } ] }
然后修改media.hbs的第一行如下:
<div class="media media-{{ modifier }}">
注意:您可以添加任意数量的变体(查看变体文档以了解更多信息)。
我们现在可能会考虑添加一些 CSS 来根据屏幕大小更改布局。 包装我们在媒体查询中创建的布局,以及为较小的设备创建单列布局。
img { max-width: 100%; } .media > .title { grid-area: title; } .media > .img { grid-area: img; } .media > .content { grid-area: bd; } .media > .footer { grid-area: ft; } .media { display: grid; grid-column-gap: 20px; grid-template-areas: "title" "img" "bd" "ft"; } @media (min-width: 600px) { .media { margin-bottom: 2em; display: grid; grid-column-gap: 20px; grid-template-columns: 200px 3fr; grid-template-areas: "img title" "img bd" "img ft"; } .media.media-flip { grid-template-columns: 3fr 200px ; grid-template-areas: "title img" "bd img" "ft img"; } }
然后,就像我们在组件中管理较小设备的视图一样,我们可以管理不支持网格的旧浏览器的布局。
在这种情况下,我将构建一个基于浮点的回退(这几乎适用于任何旧版浏览器)。 我只会为更宽的屏幕尺寸担心它,并让组件在旧移动设备的正常流程中显示。
在媒体查询中,添加以下 CSS:
.media:after { content: ""; display: table; clear: both; } .media > .media { margin-left: 160px; clear: both; } .media .img { float: left; margin: 0 10px 0 0; width: 150px; } .media.media-flip .img { float: right; margin: 0 0 0 10px; } .media > * { margin: 0 0 0 160px; } .media.media-flip > * { margin: 0 160px 0 0; }
这应该整理非网格浏览器中的显示。 对于支持网格的浏览器,您无需担心浮动,即当浮动项变为网格项时,浮动会被移除。 任何利润都会成为问题。 由于额外的边距,支持网格的浏览器中的布局现在将全部间隔开。
这是我们可以添加特征查询的地方,如果我们知道我们的浏览器支持网格,则删除边距。
@supports(display: grid) { .media > *, .media.media-flip > * { margin: 0; } .media .img, .media.media-flip .img { width: auto; margin: 0; } .media:after { content: none; } }
这样我们的小组件就完成了。 虽然是一个简单的例子——如果你需要一个后备,可能会说它根本不需要网格——它展示了我在所有项目中采用的方法,无论大小。
要将我的 CSS 文件投入生产,我们可以从公共文件夹中获取 CSS 文件并将其添加到我们的生产站点。 您甚至可以编写此过程的脚本,以便在构建时将其复制到您的站点文件夹中。
减少测试用例优先开发
我发现以这种方式工作的一个关键好处是,它确实使浏览器支持变得更容易。 不仅可以更轻松地查看该组件中包含哪些备用 CSS,而且如果我们在浏览器中遇到问题,也可以更轻松地调试它们。
当您与浏览器问题作斗争时,您通常会被告知要做的是创建一个简化的测试用例。 将问题减少到表现出问题的最小事物。 模式库中的组件通常已经非常接近简化的测试用例。 当然,这比您在查看整个网站时尝试调试问题要近得多。
除了使浏览器调试更容易之外,将您的备用代码与其余 CSS 一起包含可以更容易在不再需要备用代码时删除它,很明显,这个备用代码是针对这个组件的。 我知道删除它不会改变其他任何显示的方式。
这种易于组织我们的代码的真正原因是 Fractal 即使在小型项目中也很有意义。 鉴于我们倾向于使用 Gulp 和 Sass(即使是在较小的项目中),将 Fractal 添加到组合中并不是很大的开销。 我们不需要将其视为仅用于我们更大的项目,因为即使是小型站点也可能具有合理数量的 CSS。
见守则
我创建了一个 GitHub 项目,其中包含文章中提到的所有代码。 我建议按照文章中的描述设置 Fractal,然后从我的存储库中获取任何位 - 例如 gulpfile 或预览布局。
作为附加参考并查看一些已发布的分形项目,我们有 Perch 模式库的已发布版本,以及 24 种方式的模式库(由 Paul Robert Lloyd 构建),您可以查看它们。 这些是非网站模式库的好例子,也是用于网站的更传统的模式库。
你如何管理 CSS?
我真的很喜欢这种工作方式; 它使我能够以一种直接、逐步增强的方式编写 CSS。 根据项目的不同,我们可能包括更多的工具和文件处理。 或者,我可能正在构建一个简单的站点,在这种情况下,设置将与我们在本文中看到的差不多——对 Sass 进行一些简单的处理。 Fractal 意味着我们可以对大小站点、Web 应用程序或网站采用相同的流程。 这意味着我们总是可以以熟悉的方式工作。
这对我们有用,我希望这篇文章能给你一些实验的东西。 但是,我很想知道您和您的团队在项目中管理 CSS 的方式以及您尝试过的方法的优缺点。 我特别想听听任何使用另一种模式库解决方案开发了类似流程的人的来信。 在评论中添加您的经验。