把模块清单整合成一组文档,在大型项目中已经成为通用做法。这组文档被称为模式库( pattern library)或者样式指南( style guide)。模式库不是网站或者应用程序的一部分,它是单独的一组 HTML 页面,用来展示每个 CSS 模块。模式库是你和你的团队在建站的时候使用的一个开发工具。
1. KSS 简介
虽然创建模式库的时候不使用任何工具也可以,但有了工具的帮助会容易很多。有不少相关功能的工具库可以使用,在搜索引擎里搜索“style guide generator”,就可以找到大量结果。
接下来演示一下如何设置和运行 KSS。配置完成后, KSS 扫描样式表,找到包含 Styleguide的注释块。开发者可以使用注释块来描述每个模块的功能和用法, KSS 正是借此创建 HTML 文档的。注释块里也可以包含 HTML 片段,用来说明创建这个模块需要的标记。通过这种方式,KSS 在文档中生成了类似于截图下图所示的在线演示。
在该截图中,左侧是一个菜单,列出了模式库中的各个组成部分。右侧是下拉模块的说明文档。文档中既包含了渲染好的下拉菜单,也展示了需要用到的 HTML 标记。通过查看文档,任何熟悉 HTML 的开发者都可以轻松地复制这些标记到页面里,然后在样式表的作用下生成相应的效果。
1.1. 配置KSS
如果你还没有安装 Node.js,可以访问 Node.js 网站免费获取,然后按照指导下载安装。 Node自带了一个包管理器(叫作 npm),我们可以通过它来安装 KSS。我会把安装过程中用到的命令给你,但如果你想要了解更多关于 npm 的信息,或者遇到问题寻求帮助,可以参考 npm 文档。
1. 初始化项目
Node 和 npm 安装好以后,可以在文件系统的任意地方为项目创建目录。在终端界面访问目录,运行 npm init -y
来初始化一个新项目。 -y
选项会为项目名称、许可和其他配置自动设置默认值(如果没有使用 -y
选项, npm 就会提醒你输入这些值)。
初始化项目之后, npm 会创建一个 package.json 文件,以 JSON 格式展示项目的 npm 元数据(如代码清单1所示)。
{
"name": "pattern-library",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Keith J. Grant",
"license": "ISC"
}
接下来,我们可以安装 KSS 作为一个依赖( dependency)。在终端里输入 npm install —savedev kss,这做两件事情:一是在项目中创建了一个 node_modules 目录,其中安装了 KSS 和它的依赖;二是把“kss”写入 package.json 文件中描述开发依赖的列表( devDependencies)。
2. 添加 KSS 配置
KSS 需要一个配置文件。这个文件为 KSS 提供访问目录和文件的路径,然后 KSS 使用目录和文件来创建模式库。在项目目录下新建一个名为 kss-config.json 的文件,复制代码清单2到文件中。
{
"title": "My pattern library",
"source": [
"./css"
],
"destination": "docs/",
"css": [
"../css/styles.css"
],
"js": [
"../js/docs.js"
]
}
Source 路径告诉 KSS 去哪里寻找 CSS 源文件,然后 KSS 才可以扫描源文件里面的文档注释。KSS 根据这些注释在目标目录里生成模式库的页面。
css 和 js 字段里列出的每个文件都会被添加到模式库页面。我们已经为它们各自配置了一个 css 和 js 目录,现在就可以去创建这两个目录和里面的源文件( css/styles.css 和 js/docs.js)。文件目前是空的,很快就会向里面添加内容。
在配置过程的最后,我们将为 package.json 添加一条命令,用来通知 KSS 构建模式库。在package.json 文件的 scripts 部分添加一条记录,如代码清单3所示。
"scripts": {
"build": "kss --config kss-config.json",
"test": "echo \"Error: no test specified\" && exit 1"
},
这样就添加了一条构建命令。在终端里运行 npm run build,就可以通知 NPM 启动 KSS(在 node_modules 目录中),加载传递过来的 KSS 配置文件了。然而运行以后我们发现报错了:“Error: No KSS documentation discovered in source files.”。这是因为 KSS 找不到文档,我们现在就开始编写文档。
1.2. 编写KSS文档
KSS构建页面完成以后,左侧的目录里会添加媒体项,右侧会渲染文档。
KSS 会按照特定的方式在样式表中搜寻注释。注释中包含了标题(通常是模块名称)、描述信息、示例 HTML 代码和用来表示该模块在目录中位置的 Styleguide 注释。这几个部分之间通过空白行彼此间隔,便于 KSS 区分它们。严格来讲,只有最后的 Styleguide 注释是 KSS 必需的,但是通常我们也应该好好编写其余部分。
把代码清单4中的代码添加到 css/styles.css 样式表中,其中包含了一些基础样式和媒体模块。模块样式代码的上面就是为 KSS 提供的 CSS 注释块。
:root {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
font-family: Helvetica, Arial, sans-serif;
}
/*
Media
Displays an image on the left and body content
on the right.
Markup:
<div class="media">
<img class="media__image"
src="http://placehold.it/150x150" />
<div class="media__body">
<h4>Strength</h4>
<p>
Strength training is an important part of
injury prevention. Focus on your core—
especially your abs and glutes.
</p>
</div>
</div>
Styleguide Media
*/
.media {
padding: 1.5em;
background-color: #eee;
border-radius: 0.5em;
}
.media::after {
content: "";
display: block;
clear: both;
}
.media__image {
float: left;
margin-right: 1.5em;
}
.media__body {
overflow: auto;
margin-top: 0;
}
.media__body > h4 {
margin-top: 0;
}
在终端里重新运行 npm run build, KSS 会生成 docs 目录,其中包含一个 section-media.html文件。在浏览器中打开这个文件就可以看到模式库了。 KSS 同时也输出了一条警告“No homepage content found in homepage.md”,后面会讲解如何解决这一问题。现在我们来看看文档注释的部分,前面的几行如下所示。
/*
Media
Displays an image on the left and body content
on the right.
注释的第一行定义了这部分文档的 Media,后面的一段文本用来描述模块用途。这段描述文本可以使用 markdown 格式,因此可以根据个人意愿添加一些样式,描述也可以是多段的。
我们新建了一个模块,可以通过描述文本向其他开发者介绍使用模块时需要了解的东西。有时候简单的一句话就够了,但有时候我们可能需要说明模块依赖于 JavaScript,或者需要结合其他模块一起使用。这是与样式表使用相关的文档部分。
描述后面是一个 Markup:注释。后面紧跟一大段 HTML 代码,用来举例说明模块的用法。KSS 把这些 HTML 转化成模式库,以便读者预览效果。
警告: 要牢记 HTML 片段中不能有空白行,因为对 KSS 来讲,空白行就意味着 markup这部分结束了。
KSS 注释的最后一行需要包含 Styleguide 注释,后面跟着目录标签(本例中是 Media),如下注释所示。
Styleguide Media
*/
这必须是注释块的最后一行。如果没有这一行, KSS 就会忽略整个注释块。当更新样式表的时候,也需要相应地修改注释文档。把文档放在源代码里,这个过程就会很顺利。新增一个模块的时候,同时新增一个文档注释块。每次修改后,都要重新运行 npm run build 命令,生成模式库的全新副本。
警告: KSS 生成新页面的时候不会主动删除旧页面。如果重命名或者移动源码中的一部分文档, docs 目录下的相应文件还在原地,与新文件共存。我们刷新浏览器的时候,要确保不是在重新加载旧页面。
1.3. 记录模块变体
下面我们来记录另一类模块(如代码清单5所示)。首先引入按钮模块。这个模块提供了几种变体:两种不同的颜色和两种不同的大小。 KSS 提供了阐述多重变体的方法,可以在模式库里把每个都渲染出来。最终的效果如图所示。
按钮模块的文档注释跟之前的类似,但我们需要在标记后面添加一段新内容来描述每个修饰符(如代码清单5所示)。这是一组修饰符类的列表,每个列表项后面跟着一个连字符和相关描述。我们也可以添加{{modifier_class}}注释到示例标记上,指明修饰符类所属的位置。
/*
Buttons
Buttons are available in a number of sizes and
colors. You may mix and match any size with any
color.
Markup:
<button class="button {{modifier_class}}">
click here
</button>
.button--success - A green success button
.button--danger - A red danger button
.button--small - A small button
.button--large - A large button
Styleguide Buttons
*/
.button {
padding: 1em 1.25em;
border: 1px solid #265559;
border-radius: 0.2em;
background-color: transparent;
font-size: 0.8rem;
color: #333;
font-weight: bold;
}
.button--success {
border-color: #cfe8c9;
color: #fff;
background-color: #2f5926;
}
.button--danger {
border-color: #e8c9c9;
color: #fff;
background-color: #a92323;
}
.button--small {
font-size: 0.8rem;
}
.button--large {
font-size: 1.2rem;
}
KSS 扫描已定义的修饰符类列表,把每一个都展示到模式库中。 {{modifier_class}}指明放置修饰符类的位置。(如果你熟悉 handlebars 模板,这种语法看上去有点类似, KSS 就是使用这种语法在后台处理模块的。)运行 npm run build 重新构建模式库,然后在浏览器里查看文档。
模式库目录( docs/index.html)里现在应该已经有 3 项了: Overview、 Buttons 和 Media,其中后两个分别链接到我们之前写的对应的文档。 Overview 的链接是损坏的,因为我们还没有创建主页。这也是之前为什么会出现“No homepage content”的警告。
1.4. 创建概览页面
下面为模式库添加主页。在 css 目录下面新建一个名为 homepage.md 的文件。这是一个markdown 格式的文件,用来整体介绍模式库。复制代码清单6到文件中。
# Pattern library
This is a collection of all the modules in our
stylesheet. You may use any of these modules when
constructing a page.
现在运行 npm run build 命令,主页内容找不到的警告应该已经消失了。如果在浏览器里打开 docs/index.html,可以看到生成的内容。
你可能会注意到目录中 Overview 链接仍然不能工作,因为现在是在磁盘上直接打开模式库文件, KSS 把 Overview 链接指向了./而不是 index.html。要解决这个问题,我们需要通过 HTTP服务器访问模式库,这样./在浏览器里会链接到 index.html。可以试试 http-server 的 npm 包。
1.5. 记录需要JavaScript的模块
有些模块需要 JavaScript 配合一起工作。这时候,要为页面添加一些简单的 JavaScript 来演示模块的行为。没必要在模式库里引入一个功能齐全的 JavaScript 库。大多数情况下,切换不同的状态类就够了。我们之前已经在 kss-config.json 的配置里为页面添加了一个 JavaScript 文件。
"js": [
"../js/docs.js"
]
KSS 会把 js 数组里列出的所有脚本文件都添加到页面上。我们可以把代码写到这些脚本文件中,为模块提供最基本的功能。下面演示一下,我们将在样式表里新增下拉模块以及对应的注释文档(如代码清单7所示)。为了实现点击按钮时打开和关闭下拉框,我们要加入一些 JavaScript。然后就可以在模式库中查看模块,实现我们想要的功能(如图所示)。
/*
Dropdown
A dropdown menu. Clicking the toggle button opens
and closes the drawer.
Use JavaScript to toggle the `is-open` class in
order to open and close the dropdown.
Markup:
<div class="dropdown">
<button class="dropdown__toggle">Open menu</button>
<div class="dropdown__drawer">
Drawer contents
</div>
</div>
Styleguide Dropdown
*/
.dropdown {
display: inline-block;
position: relative;
}
.dropdown__toggle {
padding: 0.5em 2em 0.5em 1.5em;
border: 1px solid #ccc;
font-size: 1rem;
background-color: #eee;
}
.dropdown__toggle::after {
content: "";
position: absolute;
right: 1em;
top: 1em;
border: 0.3em solid;
border-color: black transparent transparent;
}
.dropdown__drawer {
display: none;
position: absolute;
left: 0;
top: 2.1em;
min-width: 100%;
background-color: #eee;
}
.dropdown.is-open .dropdown__toggle::after {
top: 0.7em;
border-color: transparent transparent black;
}
.dropdown.is-open .dropdown__drawer {
display: block;
}
运行 npm run build 构建文档,但是此刻页面还是静态的。我们把 JavaScript 加到 js/docs.js中,让页面动起来。将代码清单8添加到该文件中。
(function () {
var dropdowns = document.querySelectorAll('.dropdown__toggle');
Array.prototype.forEach.call(dropdowns, function(dropdown) {
dropdown.addEventListener('click', function (event) {
event.target.parentNode.classList.toggle('is-open');
});
});
}());
单击触发器按钮时,脚本会切换 dropdown 元素上的 is-open 类。实际项目中的完整实现需要更多的代码,需要考虑延时展现或者点击页面其他地方时关闭菜单。这里重申一下,在模式库中, JavaScript 代码应该是最少的,但是要确保打开和关闭状态的样式正确。
1.6. 为模式库分组
组织模式库是需要完成的最后一件事。菜单现在看上去还可以,因为只有几项,但是随着项目规模越来越大,就有必要为模块进行分类,以便查找。
下面为工具类添加注释文档。每个工具类都需要单独解释和展示,现在把它们归到一组。在代码清单9中,我们新增了一个文档叫 Utilities,把每个工具类作为子项添加进去,最终实现的效果如图所示。
在 Styleguide 注释中使用圆点来创建子项,类似于 Styleguide Utilities.clearfix。这样文档注释块就生成了 Utilities 项的一个 Clearfix 子项。
说明: KSS 最多支持条目层级深度为三级( 例如 Utilities.alignment.text-center)。
添加代码清单9到样式表,其中包含了三个工具类( text-center、 float-left 和clearfix)及其文档注释。注意新增了 Weight 注释,用来控制文档排列顺序。
/*
Text center
Center text within a block by applying `text-center`
Markup:
<p class="text-center">Centered text</p>
Weight: 1
Styleguide Utilities.text-center
*/
.text-center {
text-align: center !important;
}
/*
Float left
Float an element to the left with `float-left`
Weight: 3
Styleguide Utilities.float-left
*/
.float-left {
float: left;
}
/*
Clearfix
Add the class `clearfix` to an element to force it to
contain its floated contents
Markup:
<div class="clearfix">
<span class="float-left">floated</span>
</div>
Weight: 2
Styleguide Utilities.clearfix
*/
.clearfix::before,
.clearfix::after {
content: " ";
display: table;
}
.clearfix::after {
clear: both;
}
把所有的这些工具类都归类到一个主类中,它们就会形成一个分组。现在重新构建模式库,就会发现工具类都在目录里的 Utilities 项下面,点击子项就可以查看相应的页面。
警告: Styleguide 注释区分大小写。如果想把多个子项放入同一个分组目录, 要确保分组名称始终首字母大写,否则 KSS 会创建不同的分组( 例如,一个叫“Utilities”,另一个叫“utilities”)。
默认情况下, KSS 模式库的目录是按字母顺序排序的,分组内的子项也是这样。我们使用Weight 标识来改变排序。 KSS 会根据 Weight 的值来重新排序,值越大越靠后。你可以通过调整 Weight 值来更改某个一级目录项在整个目录中的位置,或者(就像示例中那样)控制某个分组内子项的排列顺序。
2. 改变编写 CSS 的方式
模式库对于小项目来说可有可无,但是实践证明它在大项目中非常有用。你如果正在开发一个有着成百上千张页面的网站,就不可能每次写的样式都一样。然而一旦建立了可复用的模块并且专门写了文档,那就为后面要写的大量的网页提供了工具。
如果你和很多开发者一起合作开发大型 Web 应用程序,难免会出现组件类名冲突和大量相同 UI 的重复实现。如果有了模式库,开发者可以很方便地找到和复用别人的样式,系统化地命名,不再出现类名冲突。
内容编辑和开发者使用你的模式库时,甚至都不需要学习 CSS,只需要对 HTML 有基本的了解即可。他们使用的时候可以直接复制模式库里的源码,放到页面里需要的地方。模块化 CSS是编写大规模 CSS 的核心,模式库是保证这些模块条理清晰、使用方便的手段。
2.1. CSS优先的工作流程
使用模式库是从传统的 CSS 开发方式转变而来的一种解决方案。不同于之前的先写 HTML页面再写样式,实现了模块化的样式,然后使用这些模块拼装成 Web 页面。这种解决方案称为 CSS 优先( CSS First)开发方式,先写 CSS,而不是 HTML。你可以(并且应该)按照模式库的方式开发 CSS,然后在项目中使用这些 CSS。开发流程大概是下面这样。
- 页面开发时,先有一个草图或者原型图或者其他可以展示页面的设计方式。
- 看看模式库。找找现有模块,如果有满足页面需求的模块就直接使用。然后从页面的外层(主页面布局和容器)开始,按自己熟悉的方式编写 CSS。如果使用现有模块可以构建整个页面,就不需要写新的 CSS 了。
- 你会发现有时候需要用到一些模式库提供不了的功能。项目开发早期这种情况很常见,到后面就会少很多。这时候就需要开发一个或几个新模块,或者现有模块的新变体。暂停正在编写的页面开发,先在模式库中实现这个模块。为新模块书写文档,确保它的外观和行为跟需求一致。
- 回到页面开发,使用刚写的新样式并且添加新模块到页面上。这种开发方式有几个好处。第一,为网站提供一致性更好的界面。模式库鼓励开发者复用已有的样式,而不是重新开发。比如说,不应该为网站上 10 个不同的页面编写 10 套不同的列表样式,我们更倾向于复用仅有的几套列表。这种开发方式会强迫你停下来思考,是否需要新的样式,现有的模块是否可以满足需求。 第二,当你按照模式库的方式开发模块的时候,你可以孤立地看待问题。你会从一个特定的Web 页面中脱离出来,聚焦在为一个模块写样式这样的单一任务上。不同于解决某个页面上的某个特定问题,思考新模块可能会用在什么地方会比较容易一些。你会创建一个更通用、可复用性更好的方案。第三,这种开发方式允许团队里一部分成员专注于开发 CSS。对 CSS 不太熟悉的开发者可以把一部分工作移交给经验更丰富的人。擅长 CSS 的开发者每完成一个模块,就可以向其他人发送一个链接,指向模式库里的模块位置。第四,这种开发方式可以确保文档是最新的。模式库的页面是你测试 CSS 修改结果的地方,这意味着这些页面会一直呈现出最新的正确行为。修改 CSS 的时候,文档恰好就在旁边的注释块里,这样很容易保持文档也是最新的(后面会谈到如何修改现有的模块)。
如何编写样式才能更好地复用到任意页面上。我们应该先写好 CSS,结构良好的 HTML 自然就有了。
2.2. 像API一样使用模式库
当你使用模式库开发的时候,相当于你正在维护一组与 CSS 交互的 API。每个模块会附带一些类名和少量的 DOM 结构。只要相关的 HTML 部分遵照这种结构来编写,样式表就会正确地渲染样式(如图所示)。
创建模块的时候, API 的结构是最重要的部分,因为后面很难再修改。当然, HTML 有可以随意修改的地方:每个元素里的内容可以任意改变。有些情况下,模块里的 DOM 元素可以增加、移除甚至改变排序(如果元素是可选的或者可以改变顺序,一定要在文档中明确说明)。 HTML还可以移除整个模块,重新写一个不同的模块。
CSS 同样也有可以修改的地方。可以做一些小的改变,比如增大内间距、调整颜色或者修复bug。也可以做比较大的修改,比如重构媒体对象,不使用浮动了,改用 Flexbox,或者重新设计模块,把横向排列改成纵向排列。只要 API 的核心部件(类名和 DOM 结构)没有改变,就可以把 CSS 任意修改成自己想要的效果。
做出的这些修改会影响到网站的很多地方跟着变化,但是只要 HTML 按照 API 的结构书写,这些变化就会符合预期。如果你想改变网站上所有的下拉菜单的外观,那么这可以很容易实现,因为网站上所有的下拉菜单都使用了相同的模块(和相同的 API),相应的变化是一致的。
1. 编辑已有模块
下面来演示一下,假设这样一个场景,你想要修改媒体模块的展示形式。之前只有一张图片,现在需要支持两张图片,文字内容的两侧各有一张,如图所示。
这需要修改一下 CSS。只要确保修改后的 CSS 仍然支持 API(这里是指,网站上现有的媒体对象只有一张图片,依旧可以正常工作),就可以随意修改样式。我们使用 Flexbox 重构模块,步骤如下。
首先,我们需要为注释块里的示例代码增添内容。旧的示例代码不要动,因为改完还要测试之前的内容是否保持不变。现在我们添加第二个示例来测试新的行为,按代码清单10来更新文档注释块。
/*
Media
Displays images and/or body content beside one
another.
Markup:
<div class="media">
<img class="media__image"
src="http://placehold.it/150x150" />
<div class="media__body">
<h4>Strength</h4>
<p>
Strength training is an important part of
injury prevention. Focus on your core—
especially your abs and glutes.
</p>
</div>
</div>
<div class="media">
<img class="media__image"
src="http://placehold.it/150x150" />
<div class="media__body">
<h4>Strength</h4>
<p>
Strength training is an important part of
injury prevention. Focus on your core—
especially your abs and glutes.
</p>
</div>
<img class="media__image"
src="http://placehold.it/150x150" />
</div>
Styleguide Media
*/
这段代码在模式库中实现了两个模块实例。重新构建模式库,然后查看生成的文档效果。在没有修改 CSS 代码之前,你会发现一个实例是好的,另一个并非预期效果。接下来继续修改 CSS(见代码清单11),让两个实例都正常工作。之后就可以看到如图展示的效果。
.media {
display: flex;
align-items: flex-start;
padding: 1.5em;
background-color: #eee;
border-radius: 0.5em;
}
.media > * + * {
margin-left: 1.5em;
}
.media__body {
margin-top: 0;
}
.media__body > h4 {
margin-top: 0;
}
模式库现在扮演了一个类似于球场围栏的角色,它可以告诉你对 CSS 的修改是否破坏了网站上已有的媒体对象,同时检验新代码是否有效。
现在可以重构 CSS 来实现新的媒体场景。修改样式表除了让第二个实例正常工作,还要确保修改过程中不破坏第一个实例。
运行 npm run build,然后打开模式库的页面查看效果,可以看到修改成功了。媒体对象现在也可以算是多功能的了。因为我们依旧支持之前的模块 API,所以就可以不用担心破坏已完成的页面。
2. 使用语义版本和重构代码
有时候为了改成想要的效果,我们不得不修改 API。这也可以,就是需要多做点工作,但是最起码可行。可以先做完修改,然后找遍整个网站或者应用程序,把每个模块实例的 HTML 都更新一下,让它们符合新的 API。但是大部分情况下较好的解决办法是弃用这个模块(需要在文档中说明一下),然后创建一个全新模块来实现所需的新功能。这样一来,旧模块可以在之前用到的地方继续工作,开发新页面就迁移到新模块,因为新的模块支持全部功能。
为了更方便地使用这种开发方式,用一个三位数字的语义版本( semver)来为 CSS 设置版本号。一旦版本号变了,开发者自然就知道模块内容改变了。
语义版本——Semantic Versioning 的简写,一种软件包版本命名格式,使用圆点分隔的三个数字表示(例如 1.4.2)。三个数字分别代表主版本号、次版本号和修订号。
我们如果只做一些小修改(比如修改 bug),就增加修订版本号(例如,从 1.4.2 到 1.4.3);如果添加了新的模块或功能,但是没有修改 API,或者某个模块被标为废弃,就增加次版本号,把修订版本号设置为 0(例如,从 1.4.2 到 1.5.0);如果过滤了一遍样式表文件把废弃模块都删除了,就需要直接跳到下一个主版本号(例如,从 1.4.2 到 2.0.0)。有时候我们做了大量的外观设计修改(比如网站重新设计了),这时候也需要升级主版本号,即使 API 还保持原样。
3. 总结
- 使用工具来存档和清点模块,比如 KSS。
- 使用模式库来记录 HTML 标记示例、模块变体和模块的 JavaScript。
- 开发模块时遵循“CSS 优先”。
- 考虑好 CSS 定义的 API,之后不要轻易修改它。
- 使用语义版本为 CSS 做版本控制。
- 不要盲目地添加整个 CSS 框架到页面上,只取自己需要的那部分。