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中的Esprima和ESLint 一样,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成为可能。
本文文字及图片出自 微信公众号
你也许感兴趣的:
- 【外评】CSS masonry 砌体布局的替代建议
- 你需要知道的现代 CSS 技巧(2024 年春季版)
- 使用 :has() 作为 CSS 父选择器及其他更多内容
- 一个 Div 能做的事情
- 基于时间的 CSS 动画
- 【外评】请帮助我们实现 CSS grid 布局 Level 3,又称“砌体 Masonry”布局
- 最漂亮的 CSS 动画背景示例及源代码
- CSS Grid 网格布局中新引入的 Fr 单位用法教程
- 60+ CSS 搜索框代码 codepen 示例
- 响应式图片
你对本文的反应是: