Facebook 是怎样提高CSS代码质量的

在Facebook里,成千上万的工程师工作在不同的产品线上,这就意味着,在代码质量问题上,我们面临着一些独特的挑战。

我们现在不仅处于一个庞大的代码基库,可怕的是这些代码还在不停的发展 —— 渐渐增加它的新特性。有时候我们想改善提高一个现有的模块,就会影响到其他的模块,那么很多就都需要重构了。

具体到CSS中,意味着我们需要处理上千份不断变动的CSS文件。

然而,我们会通过Code Review,代码样式规范以及重构这三个方面着手工作来确保不同水平的CSS代码的质量。但是在我们提交代码之前,还是会有很多无意识的错误被我们忽略。

一直到今天,我们都使用自建的CSS Linter来检测代码的基本错误以及保证一致的代码风格。尽管这么多年CSS Linter基本实现了我们的需求,但是还是存在一些问题,我们想要一个更好的方式去保证CSS代码质量,就在此讨论一下。

正则匹配,并不很好

老的linter 主要是基于大量的正则表达式规则对css的语法进行搜索和替换。正确的分析CSS是一个繁琐重大的问题,并且定期的扩展和和一些规则的变化也是一个非常重大的挑战。

这里是一些古老一点代码例子:

preg_match_all(  
    // This pattern matches [attr] selectors with no preceding selector.  
    '/\/\*.*?\*\/|\{[^}]*\}|\s(\[[^\]]+\])/s', 
    $data, 
    $matches,
    PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
    foreach  ($matches as $match)  {  
        if  (isset($match[1]))  { 
            raiseError(...);
        }

这些检测规则需要一个专门主要的匹配规则,而且非常不好维护。很难去改变,去理解。在性能上也有很大的问题。对于每个规则,我们都需要一次又一次地遍历整个文件,而去匹配不同的正则表达式。

抽象语法树

于是我们决定想搞一个更实际好用的也更精细的CSS解析器。CSS本身上来说也是一种语言,用于这些解析器去工作。总是把它当做纯文本处理也是不太合理,因此我们打算用一个抽象语法树的方式去构建一个解析器。这种新的方法在CSS性能上有很不错的提升。

比如我们的代码库中有这么一段CSS代码:

{ 
    display: none:
    background-color:  #8B1D3;
    padding:  10px,10px,0,0;
    opacity:  1.0f; 
}

你能看出这代码中存在的问题吗? 譬如某个属性名字错了, 十六进制错了, 错误的分隔符 —浏览器都会忽略不会报错的, 这样开发者就达不到自己的意图了很难找到这些小错误.

不难发现,PostCSS是一个很不错的工具,是一个合适的、基于标准的CSS解析器和优秀的模块化的体系结构。我们也选择Stylelint作为我们新的linter工具。它是基于PostCSS构建的,非常灵活,社区支持也不错。

就像JavaScript中的EsprimaESLint 一样,PostCSS和Stylelint提供了完整的AST访问方式。AST能够让你在任何条件下非常快捷简单就能访问到任何的一个节点:用了正确的类名吗?包含了正确的抽象吗?有弃用或不受支持的扩展吗?有本地化问题吗?

譬如我们的检测规则像下面这样:

root.walkDecls(node =>  { 
    if  (node.prop ===  'text-transform'  && node.value ===  'uppercase')  { 
        report({  
            ... 
        });
    }  });

我们也可以传入一些基本的函数就像linear-gradient,这里有一些例子:

// disallow things like linear-gradient(top, blue, green) w. incorrect first valueroot.walkDecls(node => { 
    const parsedValue = styleParser(node.value); 
    parsedValue.walk(valueNode =>  {  
        if  (valueNode.type ===  'function'  && valueNode.value ===  'linear-gradient')  { 
            const firstValueInGradient = styleParser.stringify(valueNode.nodes[0]);  
            if  (disallowedFirstValuesInGradient.indexOf(firstValueInGradient)  >  -1)  { 
                report({ 
                    ...  
                }); 
            } 
        } 
    });});

这个代码相比之下更容易理解,也更容易去维护。并且这种方式无论是在怎样的CSS格式化的情况下,还是申明和规则放在哪里,都能够正常工作。

Custom rules :自定义规则

我们默认使用了一些Stylelint现有的内置规则,例如: declaration-no-important,selector-no-universal, 以及 selector-class-pattern.

如何添加自定义规则的方法可以参考built-in plugin mechanism。我们现在使用的如下:

  • slow-css-properties 来警告一些性能较差的属性,例如opacity或者box-shadow
  • filters-with-svg-files 来警告在Edge中不支持SVG的过滤。
  • use-variables 来警告那些可以被内置现有的常量替换的一些变量
  • common-properties-whitelist 来检测那些可能不存在的属性。
  • mobile-flexbox to 来检测不被老版本手机浏览器支持的属性
  • text-transform-uppercase 来警告”text-transform: uppercase”,这种写法在某些地方表现不友好。

我们也贡献了一些规则additions给Stylelint,也计划最后将要发布出来。并且也将独立一个库或者更直接的放在Stylelint现有的库中。

Automatic replacement : 自动替换

我们在检测的过程中一个重要的工具就是自动格式化的。Linter 能支持补换,甚至就算有的代码不符合规则,它也会先提示你是否要根据它的规则进行替换。这个功能非常强大并且很节约时间。一般来讲,我们在最后提交代码之前都会想看一下lint报出的错误,然后通过大量的文件去修复所有的错误,尤其规则是很平常,比如字母重新排序规则。通常是更好的让一个自动格式化完成其工作,节省开发时间。

可惜的是,Stylelint并没有内嵌的自动格式化和自动修复机制,所有我们不得不重写一些Stylelint的规则来增加一个自动替换和修复的功能。与此同时,我们讨论潜在的通用Stylelint变化使所有用户更容易去改变。

Test all the things : 测试

我们老版本的linter还有一个问题就是没有单元测试。这就好比,在代码上线前不进行单元测试一样。我们面对的可能是任意格式的处理文本,因此我们也要保证我们的检测规则能够适用于真实有效的环境中,在这里我们选择了Jest framework 这个测试框架,Stylelint对它的 支持性 很好。一个简单易懂的单元测试如下:

test.ok('div { 
    background-image: linear-gradient( 0deg,blue,green 40%, red); 
}',  'linear gradient with valid syntax'); test.notOk('a { 
    background: linear-gradient(top, blue, green);
}', message,  'linear-gradient with invalid syntax');

下一步?

换一个好的CSS linter工具只是保证高质量CSS代码的第一步,我们打算添加更多的自定义的检测规则来捕获一些常见的错误,执行最佳实践,以及控制代码约定规范。我们以及在JavaScript 的校验中执行了这一个工作。所以我们没有理由拒绝。

这个linter工具已经和代码协作工具Phabricator集成了。所示的警告/建议的已经修订完成。

这个将是collaborate-and-commit工作流过程的重要一步。

一个合适的CSS解析器的另一大好处是,它可以收集有关代码库的准确的统计数据。至少使用属性/值是什么?也许他们应该被删除或被替换。最流行的是什么颜色/字体大小/ z – index吗?也许他们应该被抽象成可重用的组件和/或变量。“最重”选择器是又是什么呢?也许这又关于到性能问题了。

这些都可以提高性能和维护

关于React 和内联样式

另外就是对于React社区中存在的CSS-in-JS 这种写法,对于CSS Linter 也是一个不小的挑战,现在大部分的linter都是仅限于处理传统的CSS文件,以后将会添加对于JSX的处理规范。

Working together : 开源

我们会把这个项目设为开源,并且尽可能的提供更多的贡献。希望,这将提供一个靠谱的最好的规则和规范,每个人都可以使用它。

谢谢JS infra和webspeed团队,和所有其他人的帮助;Stylelint 的 David Clark和Richard Hallows,这两位都提供了很大的帮助和贡献;以及整个社区,让PostCSS成为可能。

本文文字及图片出自 微信公众号

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注