你真的理解什么是前端架构吗?

本文是通过阅读《前端架构设计》进行的归纳总结。在前端技术变化日新月异的今天,前端项目变的越来越复杂,前端架构也会随着我们现在千变万化的迭代需求,架构技术的演化,不仅是架构师也是我们每个开发人员都必须去关注的问题。

前端架构的由来

2014 年 10 月 13 日 CSS 开发者大会上,在新奥尔良会议中心一个拥挤的房间里“举起前端架构的旗帜”成了在一线奋斗着的开发者们共同的诉求。在那之后,那些想要弄清楚自身定位以及在公司中所扮演的角色的开发者们发现,其实他们一直扮演着前端架构师的角色, 却从来没有拥有过这个头衔,或者没有足够的信心去争取这个职位所应具有的权力。

在大会召几周之后,很多人把他们在 Twitter 上的个人简介改成了“前端架构师”。

你理解的前端架构是怎样的?

前端架构如同建筑设计,在建设过程中,建筑设计师需要设计和规划方案,并且跟进施工过程。这与前端架构师的工作有着异曲同工之妙,不同的是后者建造的是网站,而不是建筑物。比起浇筑混凝土, 建筑设计师会在设计工程构图的工作上倾注更多的精力。同理,相比编写具体的代码,前端架构师更专注于开发工具和优化流程。

前端架构是一系列工具和流程的集合,旨在提升前端代码的质量,并实现高效可持续的工作流。

前端设计架构不是一劳永逸的工作,没有任何设计在一开始就是完美的,也没有任何计划可以一步到位。

前端架构的原则

1. 体系设计

试想一下,如果一栋建筑没有明确的构造设计,所有的重要事项都由建筑工人直接决 定,那么就可能会出现这样的情景:第一面墙用石头垒,第二面墙用砖头砌,第三面墙 用木头搭,第四面墙因为追求时髦而留空。

虽然网站的整体外观和风格基调完全由经验丰富的视觉设计师决定,但前端架构师掌控 着背后的前端开发方法和系统设计哲学。通过设计所有前端开发人员都要遵循的系统规 范,前端架构师清晰描绘了产品和代码的最终形态。

一旦前端架构师建立起了系统设计的规范,项目就拥有了可以衡量代码质量的标准,否 则我们如何判断代码是否达标呢 ?一个精心设计的系统,应当具备完善的检验机制,并 做出适当的取舍,以保证系统中的代码有实质的价值,而不是简单的堆砌。

2. 工作规划

有了清晰的结构设计之后,就需要制定开发工作流了。开发人员写一行代码并且提交 到线上需要经过什么步骤?举一个最简单的例子,这个过程包括使用 FTP 登录服务器, 修改一个文件并保存。然而,对于大多数项目而言,完整的工作流可能会用到多种工 具,如版本控制器、任务调度器、CSS 处理器、文档工具、测试组件和服务器自动化工具等。

前端架构师的目标是设计出能流畅运转的系统。这个系统不仅能高效快速地启动,还可以通过语言分析、测试用例、文档记录等方法持续地提供有效的反馈,并且大幅减少由 于重复操作而产生的人为错误。

3. 监督跟进

前端架构设计绝不是一劳永逸的工作。没有任何设计在一开始就是完美的,也没有任何计划可以一步到位。客户和开发人员的需求会随着时间改变。在某个阶段运行得很好的开发流程,随后也可能需要重新调整,以便提高效率、减少错误。

前端架构师的一个非常重要的能力,就是能够持续地优化工作流程。如今各种各样的构 建工具可以让我们很方便地改变工作方式,并通知到每一位开发人员。

有些人问前端架构师是否等同于管理角色,不再需要写业务代码。前端架构师不仅要写更多代码,更要会用多种编程语言,还要使用大量的工具。代码量并未减少,只是代码的读者发生了改变。前端开发人员面向终端用户写代码,而前端架构师面向的则是团队里的开发人员。

前端架构的核心

1. 代码

归根结底,所有的网站都是由一堆文本文件和资源文件组成HTML、CSS、JavaScript的。当我们面对制作网站所 产生的大量代码时,就会发现为代码和资源设定一个期望是多么重要。

2. 流程

怎么用工具和流程构建一 个高效且避免出错的工作流是一个重要的思考。工作流变得越来越复杂,那些用于构建它们的工具也同样如此。这些工具在提高生产力、加快效率和保持代码一致性上带来了惊人的效果,但也伴随着过度工程化和抽象化的风险。

3. 测试

要构建一个可扩展和可持续优化的系统,必须保证新代码与老代码能够很好地兼容,我们的代码不会孤立存在,它们都是大型系统中的一部分,创建覆盖面广泛的测试方案,能确保老代码还能正常运行。

4. 文档

设计文档是你同他人交流的工具,来阐述你的设计决策是什么,来阐明你的设计决策是什么以及为什么你的决策是好的。如果不是团队中的重要成员要离开,几乎都不会意识到文档的重要性。

这个四个核心是构建可扩展和可持续优化的系统的基础,接下来我们将分别对这个四点进行说明(文档略)。

代码核心

这一部分将帮助我们探讨如何提高 HTMLCSSJavaScript 的代码质量,编写类、设计函数,以及声明接口。

模块化 HTML 标记

前端架构第一个挑战就是标记的规范化,初始的标记做得不好,你将要写很多不必要的 CSSJavaScript 来弥补。模块化布局,最大的好处便是易于开发和维护,更好的响应需求的变更,模块之间不相互依赖,又有一定的共性。是在合理的嵌套中保持最好的健壮性,可拓展,易维护性。

一个完整的网页应该有多个模块组成。例如头部的 topbar,底部的 copyright,内容的新闻列表,图片列表,侧部的热播排行等,他们都是一个模块,他们独立的展示了一块自己的功能。

模块化 CSS

1. 现代模块化原则

OOCSS方法 下面的代码片段展示了如何使用 OOCSS(Object-Oriented CSS,面向对象的 CSS)方法创建一个 HTML 切换。

<div class="toggle simple">
  <div class="toggle-control open">
    <h1 class="toggle-title">Title 1</h1>
  </div>
  <div class="toggle-details open"> ... </div>
  ...
</div>

OOCSS有两个主要的原则: 分离结构和外观,以及分离容器和内容。

分离结构和外观意味着将视觉特性定义为可复用的单元。前面那段简单的切换就是一个简短的可复用性强的例子,可以套用很多不同的外观样式。例如,当前的simple皮肤使用直角,而complex皮肤可能使用圆角,还加了阴影。

分离容器和内容指的是不再将元素位置作为样式的限定词。和在容器内标记的 CSS 类名不同,我们现在使用的是可复用的 CSS 类名,如 toggle-title,它应用于相应的文本处理上,而不管这个文本的元素是什么。这种方式下,如果没有应用别的 CSS 类名,你可以让 H1 标签以默认的样式呈现。

SMACSS方法 同样以切换组件为例,按照 SMACSS(Scalable and Modular Architecture for CSS)模块化架构的可扩展 CSS 方法,写出来的代码如下:

<div class="toggle toggle-simple">
  <div class="toggle-control is-active">
    <h2 class="toggle-title">Title 1</h2>
  </div>
  <div class="toggle-details is-active">
    ...
  </div>
  ...
</div>

尽管SMACSSOOCSS 有许多相似之处,但 SMACSS 的不同点是把样式系统划分为五个具体类别。

  • 基础 - 如果不添加 CSS 类名,标记会以什么外观呈现。
  • 布局 - 把页面分成一些区域。
  • 模块 - 设计中的模块化、可复用的单元。
  • 状态 - 描述在特定的状态或情况下,模块或布局的显示方式。
  • 主题 - 一个可选的视觉外观层,可以让你更换不同主题。

BEM 方法 这里同样以切换组件为例,使用 BEM(Block Element Modifier,块元素修饰符)语法写出组件代码:

<div class="toggle toggle-simple">
  <div class="toggle__control toggle__control--active">
    <h2 class="toggle__title">Title 1</h2>
  </div>
  <div class="toggle__details toggle__details--active">
    ...
  </div>
  ...
</div>

BEM 是我们要看的第三个方法,是 SMACSS 的另一个方面。BEM 只是一个 CSS 类名命名规则。它不涉及如何书写你的 CSS 的结构,而只是建议每个元素都添加带有如下内容的 CSS 类名。

  • 块名所属组件的名称。
  • 元素元素在块里面的名称。
  • 修饰符任何与块或元素相关联的修饰符。

BEM 使用非常简洁的约定来创建 CSS 类名,而这些字符串可能会相当长。元素名加在双下划线后toggle__details,修饰符加在双横杠toggle__details--active。这里的 details 是元素,active 是修饰符,这个约定使得 CSS 类名非常清晰。使用双横杠是为了避免块名被混淆为修饰符。

这种方法在 OOCSSSMACSS 里使用的好处是,每一个 CSS 类名都详细地描述了它实现了什么。代码中没有 open 或者 is-active 这样只在特定背景下才能理解的 CSS 类名。如果单独看 openis-active 这两个名字,我们并不知道它们的含义是什么。

虽然 BEM 方法看起来很累赘、很冗余 ,但是当看到一个 toggle__details--active 的 CSS 类名,我 们就知道它是表示:这个元素的名称是 details,位置在 toggle 组件里,状态为激活。

2. 其他设计原则

单一职责原则 单一责任原则规定你创建的所有东西必须有单一的、高度聚焦的理由。你应用到某个选择器里的样式应该是为了单一目的而创建的,并且能够很好地实现这个目标。

我们关注的是样式适用在哪些地方,而不是这些样式本身。让我们来看下面的例子:

<div class="calendar">
  <h2 class="primary-header">This Is a Calendar Header</h2>
</div>
<div class="blog">
  <h2 class="primary-header">This Is a Blog Header</h2>
</div>
.primary-header {
   color: red;
   font-size: 2em;
}

上面的例子就不符合单一责任原则。.primary-header 这个 CSS 类名被应用于页面上不止一个不相关的元素上。现在,primary-header责任是既负责日历的标题也负责博客的标题。

针对这个问题,一个更加可持续的方法是让每个 CSS 类名都有单一的、有重点的任务:

<div class="calendar">
  <h2 class="calendar-header">This Is a Calendar Header</h2>
</div>
<div class="blog">
  <h2 class="blog-header">This Is a Blog Header</h2>
</div>
.calendar-header {
  color: red;
  font-size: 2em;
}
.blog-header {
  color: red;
  font-size: 2.4em;
}

虽然这种方法确实会导致一些代码重复(红色字体定义了两次),但是它的可持续性带来 的好处大大超过代码重复的任何坏处。

单一样式来源 单一样式来源的方法将单一责任理论应用到更深层次,不仅每个 CSS 类名被创建为单一用途,而且每个标签的样式也只有单一的来源。在一个模块化设计中,任何组件的设计必须由组件本身决定,而不应该被它的父类名限制。让我们看看实际应用中的情况:

<div class="blog">
  <h2 class="blog-header">This Is a Blog Header</h2>
  ...
  <div class="calendar">
    <h2 class="calendar-header">This Is a Calendar Header</h2>
  </div>
</div>
/* calendar.css */
.calendar-header {
  color: red;
  font-size: 2em;
}
/* blog.css */
.blog-header {
  color: red;
  font-size: 2.4em;
}
.blog .calendar-header {
  font-size: 1.6em;
}

写这样的样式是为了当日历在博客文章里出现时,缩小日历头部文字的字体。从设计的角 度看,这无可厚非,最终日历组件会根据它在哪里而改变外观。我们称这种带条件的样式 为上下文,已被广泛应用到我的设计系统里。

这种方法的主要问题是,定义缩小字体的样式代码在博客组件的文件中,而不是单一地存在于 日历组件文件。在这种情况下,样式散落在多个组件文件里,导致很难预料某个组件放到 页面中会长什么模样。为了缓解这个问题,我建议把带上下文的样式移到日历模块代码中:

JavaScript

「如何选择完美框架?」 首先需要指出的是,没有哪个 JavaScript 框架是完美的。如今的三大框架 AnuglarReactVue 他们各有擅长的领域,也有不足之处。关注点不应该放在框架上,首先需要决定的是选择哪些工具来实现目标,而不是选择哪些框架和插件。

这一点不仅适用于 JavaScript 框架,也适用于 CMS、MVC 和 CSS 框架。

「维护整洁的JavaScript代码」 即使平时做的项目都比较简单,也要建立良好的编程规范,如果没有某种期望和规范,你的 JavaScript 文件会像断线的风筝一样不受控制,代码也难以测试和重构。

JavaScript 是一种脚本语言,这跟 HTML 和 CSS 不同。如果你忘记闭合一个 HTML 标签或者写了无效的 CSS,最坏的情况不过是页面上出现了一些小缺陷。如果你在 JavaScript 代 码里添加了太多的逗号或者忘记闭合大括号,整个网站都有可能崩溃。

由于编写恰当的 JavaScript 非常关键,最好在项目中结合单元测试使用一些格式 / 错误提示工具

流程核心

流程核心的意义在于清晰地定义前端代码从开发人员的脑海到用户的浏览器所需要经历的各个步骤。

流程包含了开发过程的各个环节,从合理的想法到可行的设计,到有效的提交,再到最终的部署。

工作流

流程的核心是工作流。工作流指的是把想法变成现实的过程,或者从产品的角度来看,就是解决 bug、需求迭代的一系列流程和方法。然而,前端工作流不是凭空产生的,关键是要结合整个团队的实际情况,在实际的开发情景中不断细化,从而制定出适合自己的工作流。

过去的工作流

过去的工作流是根据角色逐级交付的,由产品负责人交给体验设计师,投入具体的开发之后再由视觉设计师交给前端工程师。

为了创建高性能、 响应式、易用性高的网站,现代的工作流逐渐演化为:

现代的工作流

需求 工作流一般是从收集需求开始的,因为只有这样我们才能够定义出项目内容和衡量项目成败的标准。

原型设计 跟以往每个环节直接交付一个成品不同,新的工作流注重在用户交互模型、视觉设计和前端解决方案中持续迭代。

原型设计提供了一个讨论和反馈的公共空间,它把丰满的想法实现在桌面和手机浏览器 中,在原型中,想法可以成型、摒弃、重拾、打磨。

直到原型制作成本低而修改方便被确认采纳,我们才进入开发环节制作成本高而不 灵活

程序开发 在这个环节,我们的需求可以实现得更加容易和顺利。不仅是因为我们从一个经过充分验证的设计开始,而且此时我们已经拥有了实现这个设计所需要的全部标记。开发人员的工作就是收集和处理来自数据库的数据,然后把它们放置到对应的标记上。

前端工作流

从宏观角度切换到微观角度,我们来了解一个良好的原型设计流程的重要性,以保证你团队中的前端工程师都已经做好迈向成功的准备。

现在,我们不能假设所有的工程师都有相关的经验,因此前端工作流应该在新人入职时就开始运作。这样一来,理解一个新人工作时需要的所有步骤很重要,包括第一次坐下来面对代码,用新笔记本电脑写下他们人生第一行有效的代码。

必要的工具 在进入开发阶段前,前端工程师需要进行开发环境的配置:安装vscodenodeyarnnpmGitchrome等。

本地部署 配置git公钥,克隆项目代码到本地,并且使其在本地正常运行起来,除此之外可能还会安装本地数据库,配置各种各样的服务器设置,甚至修改计算机网络和设置 VPN 等。

开发 开发阶段我们需要创建一个功能开发分支,并为分支命名。当功能开发完成时,工程师可以创建一个合并请求(或者拉取请求),这等于说: "我这里有一些我认为完整的代码,请你检查一下,然后合并到主干好吗?"这会比直接合并代码到主干费时一些,但是好处是在这些代码被采纳之前,拉取请求提供了一个代码审查和意见反馈的机会。

发布 经过代码审查和测试,我们已经准备好把这些改动发布给用户。

提交编译后的资源 在版本控制器中,最好的方法是只提交少量必要的代码。这意思是说,在 Git 中忽略那些临时文件,或者那些需要下载用来编译代码的资源文件。这样做的理由是网站并不需要那些临时文件和 Node 模块。这些文件不但明显地增加了项 目文件的体积,而且还会不断引起代码冲突,因为你本地的临时文件可能跟版本中那些对应的最新文件不一致。

持续集成的服务器 使用类似 JenkinsTravis CI,它们的众多用法之一是在代码发布到服务器之前,先对代码进行一些处理。这意味着我们可以在 Git 上忽略那些编译后的资源 CI 服务器会在检测代码后执行我们的编译任务,然后再把代码发布到服务器。

测试核心

去关注构建高质量的系统和完整的测试。

测试的类型:

  • 单元测试(unit testing)
  • 功能测试(feature testing)
  • 集成测试(integration testing)
  • 性能测试(performance testing)
  • 视觉还原测试(visual restoration testing)

单元测试 是将应用程序分解为尽可能小的函数,并创建可重复的、自动化的测试用例的过程。

在同等条件下,这些测试用例应该一直产生相同的结果,它们是应用程序的灵魂,并为今后所有应用程序的代码提供构建的基础。 如果没有单元测试,不常使用的函数可能长达数月都不会被发现有 bug。相反,通过使用单元测试,我们可以在任何代码合并到主干之前就验证每个系统函数的功能,不会等到代码实际应用到产品中时还会出现问题。

功能测试 功能测试指的是,站在外部用户的角度,测试软件的某项功能。与内部代码实现无关,只测试功能是否正常。很多时候,单元测试都可以通过,但是整体功能会失败。

前端的功能测试功能测试必须在真正浏览器做,有四种方法:

  • 使用本机安装的浏览器
  • 使用 Selenium Driver
  • 使用 PhantomJS
  • 使用 Electron

持续集成 Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。

持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码集成到主干。

持续集成的好处在于,每次代码的小幅变更,就能看到运行结果,从而不断累积小的变更,而不是在开发周期结束时,一下子合并一大块代码。

集成测试覆盖率工具 Codecov 是一个开源的测试结果展示平台,将测试结果可视化。

CodecovTravis CI 一样都支持 Github 账号登录,同样会同步 Github 中的项目。

性能测试 任何测试的目的都是为了避免不流畅的用户体验,而糟糕的网站性能正是导致用户体验不流畅的主要原因之一。因此,性能测试虽然不是针对系统或视觉问题的测试,却也是测试库的重要组成部分。性能测试衡量的是影响用户使用网站的流畅程度的关键指标,包括页面大小、请求数量、 首字节时间(time to first byte,TTFB)、加载时间和滚动性能。

性能测试的关键是制定合适的预算并坚持下去。如何进行这一步,将决定你的项目测试的有效性。

视觉还原测试 借助于多种技术和流程,视觉还原测试可以有多种风格。虽然新的工具不断地被发布到开源社区,但它们通常是一小部分功能的组合。大多数工具可以归属为以下几类。

  • 基于页面的比较 - https://github.com/BBC-News/wraith 是 一 个 基 于 页 面 的 比 较 的 例 子。它 使 用 YAML 作为设置文件,因此可以很轻松地比较来自两个不同来源的一大串页面列表。
  • 基于组件的比较 - BackstopJS是基于组件比较的工具,你可以抓取独立的页面片段进行对比,这样可以写出更有针对性的测试,并防止误报。
  • CSS单位测试 Quixote 是一类独特的比较工具,用于比较 CSS 单位的差异而不是视觉上的差异。Quixote 可以设置 TDD 模式的测试用例,这些用例会设置好预期的 CSS 数值(比如字体大小为 1em,侧边栏的内边距是 2.5%),然后检测页面是否满足这些条件。
  • 基于桌面浏览器的测试 - Gemini 非常独特,它支持在传统的桌面浏览器上运行测试用例。
  • 包含脚步库文件 - CasperJS 是一个导航脚步库,可以和 PhantomJS 等无头浏览器协同工作。该工具可以和在浏览器中打开的页面进行交互。

最后,好的架构应该是:

  • 不与框架绑定
  • css 可以维护
  • html 可以维护
  • 静态 html css 最大化
  • 良好的 SEO
  • 全面完整的文档
  • 良好的可持续性

原文发布于微信公众号 - 前端infoQ(webinfoq)

原文发表时间:2019-09-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券