DeveWork

Stylelint 在提升企业微信 CSS 代码质量上的实践

导读:本文描述了企业微信前端团队是如何借助Stylelint 做自动化Code Review 以提升CSS 代码质量。内容包括从一开始的技术选型到开始制定规则,再然后如何在团队中进行有效的推动及配合svn hook 等探索各种自动化的监督机制。其中遇到的难点在于如何处理旧的项目代码,以及如何进行有效的成员监督。希望企业微信团队的实践能对处于CSS 代码质量焦虑的团队有借鉴意义。

企业微信项目自立项开始一直处于快速项目迭代状态中,于前端团队而言,企业微信的前端开发需求主要集中于两个Web 后台管理端。除此之外,还包括多个产品版本(云端版本,私有化版本等)及其若干分支在并行开发。开发人员构成上,涉及到全国多个区域的开发团队。从上半年开始,我们渐渐感受到因为项目的高速迭代,在时间、人力上不足以支持人工Code Review 的现状下,CSS 代码越来越处于失控的状态。从那时开始,我们有了借助lint 工具做自动化Code Review 的想法并开始付诸行动。

技术选型:毫无意外的 stylelint

不同于百花齐放的JavaScript Lint 工具,开源社区上关于CSS Lint 的解决方案能找出的也就这三个:csslint、SCSS-Lint、Stylelint。很自然的我们选择了Stylelint,原因如下:

推动计划:三部曲战略

为了能让stylelint 为基础的自动化Code Review 体系在团队项目中落地生根,我们一开始便制定了三个时间段的阶段推进目标,称为“三部曲战略”:规则制定期-规则过渡期-规则自动化监督期

1)规则制定期:本阶段将先定制企业微信规则文件,并要求项目组成员在编辑器或IDE 安装stylelint 插件辅助日常开发中使用。使用过程中可针对遇到的情况进行讨论并适当调整具体stylelint 规则。

2)规则过渡期:本阶段要求成员在开发过程中抽出时间对以前项目的代码进行优化。

3)规则自动化监督期:本阶段将探索建立相应的自动化监督机制并在日常开发中施行下去。后面实质上产生了两种方式,一种是配合SVN Webhook 做实时监测代码提醒,另外一种是一个监控报表工具展示每日产生的代码质量情况。

“三部曲战略”的计划后来在实际情况中,其时间段并不是严格按照先后顺序推进。在下文中,将逐一对每个阶段的进行说明。

Stylelint 规则的制定

在第一阶段的“规则制定期”,我们的首要任务是制定出属于企业微信CSS 开发团队的Stylelint 规则。

一份Stylelint config 中,规则的来源有如下三种:stylelint 自带规则,扩展第三方规则,自己写插件自定义规则。

stylelint 自带超过百条规则,用户可以根据个人实际情况进行启用并做个性化的设置。如在企业微信中,我们规定了一些为0 的CSS 值不能带单位,如 font-size: 0px; 需要为 font-size: 0;。采用length-zero-no-unit 这个stylelint 自带的规则就能满足我们的需求。

实际运行效果如下(终端中运行stylelint):

第三方规则则更多来自于开源社区,通过插件(plugin)或扩展(extend)的方式集成到自己的规则表中。而如果前面两种方式都满足不了,那么可以通过自己编写插件来自定义规则。

在企业微信中的规范中,我们有几个特别的需求点是当前社区上没有相关轮子的,为此我们编写了一个stylelint 插件来承载我们的需求。

比如,我们会要求开发者在新建一个SCSS 文件的时候必须在文件头添加注释,注释内容包括文件名,@data@author 等关键信息。为此我们写了一个Stylelint 规则comments-in-header。如果你熟悉PostCSS 插件的开发,那么是很容易就写出相关的检测逻辑出来,附该stylelint 规则的核心检测代码:

// 注释肯定是在第一或第二个代码段位置,
// 换而言之,第一个肯定是 @charset "UTF-8"; 或者注释
if (root.first && root.first.type === 'atrule' && root.first.name === 'charset') {
  // 当第一个节点为 @charset "UTF-8"; 的时候,第二个节点应该为文件注释头
  if (root.nodes[1].type === 'comment') {
    let targetComment = root.nodes[1].toString()
    // console.log(targetComment,vaildComment(targetComment))
    if (vaildComment(targetComment)) {
      return
    }
  }
} else {
  if (root.first && root.first.type === 'comment') {
    let targetComment = root.first.toString()
    // console.log(targetComment)
    if (vaildComment(targetComment)) {
      return
    }
  }
}

实际运行效果如下(WebStorm 中):

又比如,我们在业务的SCSS 文件中,会严格要求里面的CSS 类名在命名上要跟随SCSS 文件名(即命名空间以SCSS 文件名做依据)。为此我们也写了一个规则namespace-follow-filename。在实际使用中,有些文件或特定目录下的代码文件是不用遵循这个规则的,为此我们配合stylelint的option API 做了选项,其支持正则表达式。

{
'wechat-work/selector-namespace-follow-filename': [true, {
      'fileDirWhiteList': ['singlePage', '/^widget/', 'component'],
      'filenameWhitelist': ['/^base/', '_special', 'widget']
    }
}

运行效果如下(VS Code 中):

针对企业微信项目中产生的自定义规则,我们已经对外整理为一个插件stylelint-wechat-work-css 发布到了NPM 并将源码托管到Github 上面(stylelint-wechat-work-css),如果你有兴趣,可以点个star。

另外,我们企业微信项目中使用到的stylelint 规则文件也已经开源到Github 上面,如果有兴趣也可以前往参考。

难题:旧的代码如何修改

第一阶段开始我们要求团队成员在日常开发过程中开启编辑器或IDE 的stylelint 插件,这样确保新产生的代码都是已经lint 过的。随后进入到第二阶段的“规则过渡期”,一个绕不过的难题是如何处理旧的代码。

对于旧的代码,我们没有选择忽视。在这段一直持续到现在都没有结束的“规则过渡期”,我们的做法是:

1)新的需求如果刚好是需要改到旧的文件的,一并将该文件的代码给按照新的规则修正过来。

2)其它文件只能是额外抽出时间分配人力进行修改。

而在具体如何修复旧的代码上,我们也总结出了一套如下的方案:

1)善用stylelint 的 fix 功能

stylelint 支持带—fix 参数去修改能自动fix 的规则。如上面谈到的length-zero-no-unit 规则,执行带—fix 参数的命令后会智能将目标文件中的0px;改为不带单位的0

2)编辑器或IDE 的格式化代码功能

旧的代码有不少是不合缩进规范的,缺少相应的空格等等。这个时候用编辑器或IDE 的格式化代码功能可以很容易就修正为标准格式的代码。我们使用的是WebStorm 的Code Formate功能,其能根据你的stylelint 规则去格式化代码。在这个第二阶段的“规则过渡期”,我们发现这种代码格式问题占据了不少大头,我们也强制要求成员启用 editorConfig

3)批量修改

虽然没有具体统计,但上面两条其实已经解决了约八成的修改工作量。剩下的需要善于借用一些IDE 的批量替换功能去批量修改。如我们之前因为历史原因,sass 代码采用@mixin 去做浏览器前缀的处理。在后来接入autoprefixer 之后,不少SCSS 文件还残留着这些mixins。我们为此新开发了一个stylelint 规则unused-mixins,在修改上,借助webStorm 的批量正则替换功能很容易就换掉了。

4)善用Stylelint Ignore

修改旧的代码,风险必然是存在的。在企业微信的CSS 项目上,因为之前团队成员还把控得不错,所以我们也有底气去修复大部分旧的代码。但也确实是有一部分代码因为历史遗留问题啃不动或者啃了会影响线上业务的。而在项目的实际情况中,也有免Stylelint 检测的需求,比如在CSS 代码中引用了第三方组件需要覆盖相关CSS 样式,第三方组件的代码规范与当前项目不同,但不可能去改第三方组件源码。这个时候我们需要借助Stylelint Ignore 去处理。

/* stylelint-disable */
/* (请说明禁止检测的理由)前端组件限制类名 */
  .cropper_topContainer .img-preview {
    border: 0 none;
  }
/* stylelint-enable */

如上面的代码,通过stylelint-disablestylelint-enable的注释段就能让其包裹的代码段免stylelint 检测。这个功能在一定程度上有被滥用的风险,所以我们会要求写这个注释的时候也要加上相应的理由。

自动化监督体系的建立

从一开始,我们便期许建立起一个以stylelint 做技术核心的、无差别(人人平等)的、自动化运作的团队规范约束体系。这个体系如果要起作用,是需要有监督角色的。不然项目组人员大可率性开发,直接无视要求安装stylelint 插件这类要求。之前我们也考虑过如下两种非人工监督方案:

1)在目前使用的Sass 编译的工作流中加多一个任务,实时对改动文件进行lint,有报错直接提醒并终止编译。我们目前的工作流是基于gulp的,也很容易找到相关的gulp 插件gulp-stylelint。但这种是最快否决的,因为它对Sass 编译速度有影响且“有报错直接提醒并终止编译进程”的方式不怎么友好。

2)通过hook 的方式,提交代码前进行lint 相关任务,有报错不予commit 代码进去。我们的项目是通过svn 托管代码,服务端svn webhook 可考虑;如果是客户端hook 就跟要求成员安装stylelint 插件一样,如果成员积极性不高,一样也是形同虚设。

约束(监督)体系至少应该是半自动化的,第三个阶段“规则自动化监督期”应运而生。我们做了不少探索,这是目前正在使用的两套方案。

1)配合SVN 服务端 webhook 做实时监测提醒

公司内部的SVN 服务器有提供webhook 接口,笔者配合其中的“Post-Commit”接口(目标路径下产生SVN 提交后触发的钩子)开发一个实时stylelint 监测代码并提醒的简易web server 运行在内网机器上。运行机制如下图:

每当有项目成员commit 代码到SVN 服务器目标路径,webhook 被自动触发,内网部署的web server 相应在某个地方重新svn up 这些文件并执行stylelint 检测,如果检测到错误,则将检测结果自动发送邮件及企业微信消息到该成员上,提醒其尽快进行修改。

2)每日报表汇总提醒

另外笔者借助Vue 开发了一个简易的报表系统,其配合crontab 服务做定时任务。实现的功能是:每天定时(10:00 AM)svn up 整个项目CSS 代码并执行stylelint 检测,产生按日归档的报表并通过内网可访问页面的形式展示。报表页面是项目组成员人人可访问,错误信息一目了然。如果错误代码没有被修改,则会一直展示在页面上。

报表页面截图如下:

因产生的报表信息是带svn author 等相关人信息的,同时也会自动发送邮件或企业微信消息提醒到相关人上。实际效果截图如下:

以上两套监督方案,集“展示+提醒”功能为一体,因为是自动化运作,日常监督对项目成员人人平等。当然,这些监督手段仅限于提醒作用,因此虽然实现了自动化,但必要时候还是要靠人力去监督,比如不时催促下相关成员去维护旧代码。

总结与未来

效果评价

我们从半年前开始执行这套以stylelint 为基础的自动化Code Review 及监督体系,以期提升企业微信项目CSS 代码质量。过去半年,虽然没有具体量化的数据,但总体而言是有效的。其效果体现在如下几点:

1)有效弥补人力Code Review 带来的问题。过去项目主要靠人工Code Review,需要额外安排时间去进行,在跨团队合作上人工Code Review 的做法更常常是无能为力。这套体系建立起来后,跨团队的规范遵守处于可监督状态,部分解放人力,也避免了人工Code Review 上可能产生争议的情况。

2)代码整体质量可控。这套系统建立后,每日产生的代码质量情况可见,从苗头上进行提醒勘正,有效避免了烂代码越积越多而后积重难返的情况。因为严格到代码格式,整个项目的代码可阅读性、可维护性自然而然也起来了。所以后来即使企业微信因组织机构变化而产生开发人员调整,整体项目代码可以称为处可控状态。

3)意外的收获:促进了企业微信的CSS 开发文档的产生。在此之前,我们的开发文档只是写过几篇零零散散放置在几个地方,其余是仅处于口耳相传的阶段。这个代码质量提升行动直接促使我们产生企业微信的UI 开发文档,包括CSS 规范及开发资源整理等。

争议、展望

基于Stylelint 完全做自动化Code Review,严格意义上讲是一件理想化的事情,毕竟其功能还是有限(比如说某个CSS 写法更优或劣是无法检测出来)。但在一个开发团队中,来来去去经历多人开发,每个人水平不一,而需求总是层出不穷。于团队运作而言,我们的追求其实很简单:在遵守共同制定的规范上前行,更好的CSS 能力依赖于个人追求与人工Code Review 的促进。

从另外一个角度来说来讲,工程师也许更喜欢让机器去解决一些事情,比如说本文一开头提到的0px0的写法,写成0px还是0本质上于项目而言无关痛痒,许多开发者更习惯用工具去后处理这些细节问题。但是我们认为,只有纠结细节才能让团队成员从整体上形成代码规范意识,并建立起一个团队的“纪律性”。在Web 开发领域,从来都不缺各类工具,我们常常欠缺的是那种自我严格要求、自我Code Review 以及尊重团队协作的意识。工具只是一种手段,再严格的规则,优秀的开发者即使去掉这些工具也能写出满足规则的代码。建立起这些意识也许会有短期的痛苦,但我们仍然认为是值得的。

展望未来,在企业微信项目的CSS 代码质量上,除了需要再花点时间维护旧项目代码,我们将制定更多的自定义规则,持续完善当前代码规范,巩固当前有效的约束体系并探索更多可能。

(全文完,原项目成员ka***i,jo**su 亦对本文有贡献,感谢设计师jam**ou 提供的头图)

版权声明:本文从未授权任何公众号发表文章,请相关公众号在转载前联系本人授权,否则视为侵权。