去年我加入了 Hy-Vee,并将大部分的时间花在了前端开发和设计之中。刚入职不久,我发现我们需要一个统一的组件库,紧接着我就开始着手做这件事情。从这之后,我学到了很多关于设计规范、设计系统、组件库相关的知识,并且也摸索出一套最佳实践方法。在这篇文章中,我将总结这一年所学到的东西,希望能给你带来一些启发。

为什么?

每个网站在最开始的时候都是很简单的,可能只有一个页面和几个不同的模块,功能也不复杂。

网站最初都很简单

慢慢地,它开始变得复杂。

越来越多的页面,越来越多的功能。甚至于,你们需要一个或多个小组一起工作,每人负责一小部分,还需要考虑它在不同设备中的适配问题。

接着,你可能会发现有些地方的按钮和其它地方的有一些不同,或者某个小组想要开发一个新功能时发现另一个小组已经做了。你们的产品不再保持统一了,团队间也无法保持信息同步。

那么,这个问题是可以阻止的吗?当然可以。但是为什么它还是一次又一次地发生了呢?除非在设计开发过程中每一次你都能提前预判到这些问题,否则在你们产品不断壮大的过程中将会一直面临这类问题。

为了避免这些问题,修复那些不统一的地方,其实你可以做这三件事:

  • 指定样式规范
  • 创建共享组件库
  • 构建设计系统

如果你对这些事务还没有概念也不用担心,下文我将会把我这一年所学的东西,所尝试的方法和所遇到的错误都详细记录下来。

样式规范是什么?

设计规范

样式规范是你的产品将如何展现的一系列规则,包含视觉(设计、图像)上的以及内容(声音、语调)上的。

有了样式规范,你的团队才可以多人一起设计出一致的、清晰的产出物。几乎所有产品或品牌都会有自己的样式规范,虽然有很多不是公开的。

什么是共享组件库?

共享组件库

共享组件库是样式规范的一个动态的、可实时交互的实现。它是一份和品牌设计感一致的共享组件,开发人员可以直接用它构建产品界面。它所带来的一些好处包括:

  • 有了共享样式库意味着更多的共享代码,更少的定制化代码。
  • 因为它有一个中心唯一的源,所以你可以很容易地保证所有使用共享组件的地方都能满足无障碍特性。
  • 所有人在同一个地方给组件增加新特性,或者修复 bug,让组件更加健壮易维护。
  • 更少的代码复制可以让你更快地开发出新产品,代码重构也变得简单。

设计系统是什么?

设计系统是一系列规范、文档、原则以及依照这些规范所实现的组件。它是样式规范和共享组件库所结合的产物。最流行的设计系统是谷歌的 Material Design

设计系统

一个设计系统应该包括:

  • 内容 -- 构成用户体验的基本内容。
  • 设计 -- 设计系统的视觉元素。
  • 组件 -- 各种产品功能的基本构建模块。
  • 模式 -- 独立的可交互模块。

从哪里开始?

在不同的公司,会有不同的起点。下面是我们构建设计系统的一些大致步骤:

  • 整理当前所有已存在的设计模式。
  • 拟定设计原则和样式规范。
  • 确定设计 design token。
  • 创建一系列图标。
  • 选择开发语言或框架。
  • 评估是使用 Monorepo 还是独立打包。
  • 评估是使用 CSS/Sass 还是 CSS-in-JS。
  • 创建组件库。
  • 选择文档平台。
  • 编写设计系统文档。

构建设计系统

整理当前所有已存在的设计模式

除非你们是完全从零开始构建,否则的话,你就需要从已有的设计中找出所有的设计模式,并标注不一致的地方。这样做的目的是为了保证不管用户在使用产品的哪个部分时都能获得一致的用户体验。

接下来开始记录并整理其中你们团队最喜欢的一些交互模式,这将会产出一份你们需要的样式规范。

拟定设计原则和样式规范

接下来,将上述产出物做成一份初始的样式规范。你可以使用下面这些工具:

我推荐使用 Sketch,但是你可以根据团队情况来自由选择。在此之前,请确保每个团队成员都能理解 Symbol 的最佳实践以及何时使用嵌套

确定 design token

design token

design token 是设计系统的视觉原子。一般来说,它是由一系列属性名和属性值组成的实体。我们会将一些公共视觉属性统一存储在 design token 里面,比如说主色 #00CCFF,基本字号 16px,这样设计系统的维护就很方便,也能在产品不断壮大的同时保持视觉上的一致。

它包括如下这些内容:

举个例子,让我们看一下 Zeit 的设计系统——Geist——的 design token:

--geist-foreground: #000;
--geist-background: #fff;
--accents-1: #fafafa;
--accents-2: #eaeaea;
--accents-3: #999999;
--accents-4: #888888;
--accents-5: #666666;
--accents-6: #444444;
--accents-7: #333333;
--accents-8: #111111;
--geist-success: #0070f3;
--geist-error: #ee0000;
--geist-warning: #f5a623;
--dropdown-box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.02);
--dropdown-triangle-stroke: #fff;
--scroller-start: var(--geist-background);
--scroller-end: rgba(255, 255, 255, 0);
--shadow-small: 0 5px 10px rgba(0, 0, 0, 0.12);
--shadow-medium: 0 8px 30px rgba(0, 0, 0, 0.12);
--shadow-large: 0 30px 60px rgba(0, 0, 0, 0.12);
--portal-opacity: 0.25;

现在,我们就有了第一份能够和开发一起共享的设计语言。

创建一系列图标

创建一系列图标

如果你们已经有了一套图标,只需要决定要保留哪些就行了。一般如果公司体量不大,或者优先级不高的话,自己亲力亲为做一套图标没有太大意义,你完全可以使用一些成熟的开源图标库。这里有一些示例:

无论你是准备自己做一套图标还是使用开源图标,你都应该让整个交付流程更加标准化一些。还有一点需要考虑的是,你要明确图标的最终使用者是谁,是设计师、开发者还是市场人员。

我推荐使用 SVG,因为它可以保证你的图标库具有唯一数据来源。使用 SVG 的话,你的工作流程可能是这样的:

  • 设计师提供原始 SVG 文件。
  • 使用 SVGO 优化或者压缩文件.
  • 使用 SVGR 等技术为开发人员自动创建可用于生产的 React 组件。
  • 为市场人员生成不同尺寸和分辨率的 PNG 或者 JPEG 文件。
  • 将 SVG 打包成字体文件用于移动端。

选择开发语言或框架

当前项目支持使用什么语言和框架?组件库支持的是哪一种?最好先考虑一下这些问题,这样才能够从底层为你的组件库做出更好的技术架构。

如果你们的项目还在使用 <script> 引入文件,而不是使用 ES Modules,那么你就需要将组件库打包成单个文件。此时,你的 HTML 文件中可能会有这种 <script> 标签引入的代码。

<script src="/js/component-library.min.js"></script>

这样的话,你就需要类似于 WebpackRollup 这样的打包工具。我推荐使用 Webpack,因为它是比较通用的行业解决方案。

评估是使用 Monorepo 还是独立打包

Monorepo 可以让你在一个仓库中发布多个代码包,它确实解决了一些实际问题,但也额外带来了一些其它问题。理解他的优势和劣势很重要:

Monorepo

优势

  • ✅ 公用代码都在一个代码仓库中,最终打包的体积很小。
  • ✅ 共用的构建、测试、和发布代码,不用到处复制。
  • ✅ 更加细粒度的 semver 控制。

劣势

  • ⛔️ 需要额外的工具和基础设施代码。
  • ⛔️ 使用者需要从不同的代码包导入。
  • ⛔️ 更多边缘化的技术,你将会遇到很多比较小众的问题,这种问题可能在网上找不到答案,需要自己摸索。

下面有一些问题可以帮助你判别哪种方式更适合自己的团队。

  • 你们是不是有多个代码库发布至 NPM。
  • 你们的构建、测试和校验代码是否复杂?它们是不是被同时应用于多处。
  • 你们将会有多少代码仓库(或者是明年计划有多少)?
  • 你们的团队有多大?有多少人可以投入到组件库的工作中?
  • 它是否会被开源?有没有类似的成熟方案可以参考?
  • 现在有没有看到未来多仓库的需求?(比如图标、codemods等)

对于很多小公司来说,Monorepo 可能没有太大必要,它适用于特殊的时间段和地方。我们在 Hy-Vee 大约有 150 个开发者,使用它构建大约十个不同的代码库,来服务于内部和外部的 APP。

注意:想要使用 Monorepo?可以看一下创建一个基于 Lerna 和 yarn 创建一个 Monorepo 这篇文章。

评估是使用 CSS/Sass 还是 CSS-in-JS。

我更喜欢 CSS-in-JS,尤其是 styled-components。要想使用 CSS-in-JS,这里有很多可供选择的技术栈。styled-components 针对组件库最主要的优势如下:

  • 感觉上就像编写传统的 CSS 代码和 JavaScript 对象。
  • React 和 React Native 都支持。
  • 自动添加兼容性前缀。
  • 作用域内的样式避免了全局的样式冲突。

如果你们的组件库还需要输出原生的 HTML/CSS 代码,我推荐你结合 Sass 一起使用,具体可参考 IBM 的 Carbon Design System

一个按钮

<button class="bx--btn bx--btn--primary" type="button">
    Button
</button>

不管你最终选用了何种技术,我还是推荐你提取出公共 CSS 变量,将其保存在 design token 之中。

创建组件库

当你们已经确定了技术选型,并且开始制定样式规范,现在就可以开始创建组件库了。我推荐使用 React 和 Storybook

2019 年的组件式开发工具技术栈

创建一个共享组件库绝非仅仅是将样式规范转换成代码那么简单。

你需要考虑组件库的使用者如何和这些组件交互,比如说他们希望提供什么样的 API,怎样能够更清晰并且从代码中就能看出使用方式。举个例子,就说说最常见的按钮,它会有很多种不同的类型,比如主要按钮、次要按钮,等等。

按钮的不同状态

你是要给每种按钮写一个组件?

<PrimaryButton>Hello World!</PrimaryButton>
<SecondaryButton>Hello World!</SecondaryButton>

还是使用组件属性来区分?

<Button>Hello World!</Button>
<Button variant="secondary">Hello World!</Button>

其次,你要给这个属性叫什么呢?是 variant 还是 type?有没有考虑过其实 type 是一个 HTML <button> 保留属性?这只是其中的一些问题,还有很多类似的问题需要考虑:

选择文档平台。

最终,你需要选择一个文档平台来展示你的设计系统,如果你之前没接触过,我推荐使用 Storybook Docs

Storybook Docs

它可以产出其它几个类似平台的效果,比如 DoczStyleguidistDocusaurus

  • 易于从示例组件中复制代码片段。
  • 根据 prop-typesdefaultProps 自动生成属性列表。
  • 很容易查看一个组件的各种状态。
  • 使用 MDX 格式来编写自定义的组件示例。

Storybook Docs 流程

如果你想要在文档中就用上你的组件库,或者展示不同状态,那么我推荐你使用 MDX 格式编写文档,因为它允许你在 Markdown 中插入 React 组件代码。

当然,你也可以选用一种完全自由可控的文档方案,比如说让我印象深刻的是 Atomize React

Atomize React 文档

编写设计系统文档

好了,你已经选好了文档平台,现在可以开始编写文档了。这份文档应该包括:

  • 共享组件库的安装和启动方式。
  • 每个组件的属性列表和类型定义。
  • 组件代码示例,以及可以复制的代码片段。
  • 技术选型及其大概原理。

还可以考虑添加:

  • 一个包含教程的开始章节。
  • 一个完整的可选模板。
  • 主题相关信息。
  • 一个在线代码演示平台(Codepen 等)。

总结

这一年,我花了大部分的时间来思考设计系统,并从零开始构建了一个完整的设计系统,这段经历让我对设计和前端开发的工作都有了新的认识。最后,我将给出一个给了我很多灵感的设计系统相关的列表,希望能帮到你。

资源