原文链接:dev.to/dcwither/ev… 作者:Devin Witherspoon
在SCSS中编码,就像在任何其他编程语言中一样,应该始终有一个优化可读性而不是写入速度的目标。不幸的是,SCSS中的一些语法可能会使read/understand.变得更加困难这方面的一个例子是父选择器(&)。
父选择器对于伪类(例如&: hover)和以灵活的方式使用上下文(例如: not(&))很方便,尽管我们也可以滥用它来创建“联合类名”。
.parent {
&-extension {
}
}
这种用法带来了一些问题:
-
您无法在代码库中搜索超本标记语(父扩展)使用的结果CSS类。
-
如果您在一个较大的文件中使用此模式,您可能需要查看多个嵌套级别,以便在心理上计算结果CSS类。
本文遵循创建dcwither/scss-codemods的Union-class-name命令的持续过程,目标是消除代码库中约2,000个Union类模式实例。
未来打样
为了限制现有模式的传播,我在项目中引入了选择器-无联合-类名Stylelint SCSS规则。不幸的是,这并没有修复整个代码库中现有的2000个此模式实例。为了进行更广泛的修复,我求助于Post CSS。
Post CSS的救援!
我的想法是写一个Post CSS脚本来“促进”嵌套规则,这些规则以&-开头,在它们的父上下文之后。
第一步:这应该很简单,对吗?
使用AST Explorer作为实验工具,我玩了转换,直到我发现一些看起来有效的东西:
export default postcss.plugin("remove-nesting-selector", (options = {}) => {
return (root) => {
root.walkRules((rule) => {
if (rule.selector.startsWith("&-")) {
rule.selector = rule.parent.selector + rule.selector.substr(1);
rule.parent.parent.append(rule);
}
});
};
});
我注意到的第一个问题是脚本颠倒了它所推广的类。这可能会改变应用冲突CSS规则的优先级,从而导致行为的改变。
.some-class {
&-part1 {
}
&-part2 {
}
}
// becomes
.some-class {
}
.some-class-part2 {
}
.some-class-part1 {
}
如果这些类不被相同的元素使用,这可能不是问题,但是如果没有相关的超文本标记语言,我们就无法知道情况是否如此。
第二步:好吧,让我们修复那个错误
所以我们需要做的就是维持提升的阶级秩序,对吗?
export default postcss.plugin("remove-nesting-selector", (options = {}) => {
return (root) => {
let lastParent = null;
let insertAfterTarget = null;
root.walkRules((rule) => {
if (rule.selector.startsWith("&-")) {
const ruleParent = rule.parent;
rule.selector = ruleParent.selector + rule.selector.substr(1);
if (lastParent !== ruleParent) {
insertAfterTarget = lastParent = ruleParent;
}
ruleParent.parent.insertAfter(insertAfterTarget, rule);
insertAfterTarget = rule;
}
});
};
});
现在提升的类保持它们的顺序,但是转换后的SCSS无法构建,因为引用它们的地方不存在SCSS变量。
.some-class {
$color: #000;
&-part1 {
color: $color;
}
}
// becomes
.some-class {
$color: #000;
}
.some-class-part1 {
color: $color;
}
这就是我开始意识到这个问题的复杂性的地方。变量可以引用其他变量,所以我们需要处理这个递归。名字冲突呢?如果我打破了一些已经在工作的东西,试图修复其他东西呢?
第三步:是时候建立一些结构了
我不打算在一个下午和AST探索者一起完成这个项目。在这一点上,我决定将项目移动到GitHub存储库中,这样我就可以管理增加的复杂性。
从这里开始,开发过程变得更加正式:
-
为现有代码编写测试。
-
为我想实现的功能编写测试存根。
-
创建了一个GitHub项目(看板)来跟踪任务。
-
开始考虑其他人可以使用的CLI。
-
在READ ME中记录预期行为。
尽管我是唯一一个从事这项工作的人,但随着项目的发展,有必要遵循这些实践,因为我不再能把整个项目和行为记在脑子里。
验证中
单元测试虽然有助于记录和验证假设,但不足以确保转换不会对生成的CSS产生任何负面影响。通过在转换前后编译SCSS,我们可以区分CSS以确认没有变化。
diff --side-by-side --suppress-common-lines \
<(grep -v "/\* line" [before_tranform_css]) \
<(grep -v "/\* line" [after_transform_css])
如果你对我做的更复杂的测试感兴趣,你可以查看用Jest扩展编写更干净的测试。
到目前为止所有的虫子
那么我意识到我一路上错过了什么?
-
给定选择器中的多个嵌套选择器。
-
需要与提升的规则一起提升的范围变量。
-
在分组选择器(. a,. b)中,每个成员必须以&-开头才能提升规则。
-
不考虑嵌套分组选择器的乘法因子(参见此测试)。
-
重复作用域SCSS变量。
-
提升规则可能会更改已编译CSS中规则的顺序。
-
将SCSS变量提升到全局范围会影响其他文件。
-
SCSS变量可能具有相互依赖性,并且可能需要递归提升。
-
关于变量的一切都适用于函数和混合。
学习再学习
这个项目还没有完成,但它已经完成了从一个下午在网络编辑器中编码到拥有必要的基础设施和测试以继续自信地开发的过程。
这里的一般教训是,实现一个想法所需的工作通常比你最初想象的要复杂得多,我发现自己不时地重新学习这个教训。因为我已经有一段时间没有花太多时间在SCSS上了,所以变量、混合和分组选择器并不是我的首选。我对语言和问题(嵌套和父选择器)有一个短视的视角,这使得问题看起来比现实中简单得多。
好的一面是,当我意识到这个问题需要一个更复杂的解决方案时,我适应得很好,逐渐增加了解决方案的过程。将假设、需求和规范从我的脑海中转移到code/tests/project板中,使整个项目更加易于管理。另一个学习是,我不再假设这种转换是正确的——它只是正确到在我遇到的场景中有用。