《人人FED CSS编码规范》中,对CSS的编写方式进行了严格的约束和说明,但是如何在实际开发过程中,确保代码能够严格遵循规范的要求,是规范制定者必须要考虑的问题。
人为遵守规范,是规范制定的初衷,也是最终目标,但是在规范没有变成习惯之前,人为遵守终归是“不靠谱”的。因此:“无工具,不规范”,有了规范,必定需要配套的工具来确保规范的执行。
说起来也比较奇怪,同样是前端开发的三大语言之一,CSS的检查工具备受冷落,而JS的代码规范检查工具则层出不穷。JSLint/JSHint/gjslint等神器不断涌现,且检查的严格程度令人瞠目结舌,但是CSS的检查工具却寥寥无几,仅有的一些检查工具,也只对CSS的取值、是否符合W3C规范进行了检查,对于代码风格只字不提,而各大互联网公司的开发规范中,对于代码风格是有严格约束的。因此:一个严格按照规范检查CSS代码风格问题的工具是必不可少的。
另外,CSS的检查、修复、排序、合并、压缩等功能,是相互关联的,并不是相互孤立的。比如:只有进行了完整的CSS代码修复与合并,才能最大限度的实现压缩。而目前已有的CSS检查、属性排序、压缩等工具,实现方式五花八门,实现语言也是多种多样,不利于把CSS相关的功能有机的整合在一起,也不利于工具的集成和功能的扩展。因此:依据完善的开发规范,实现一个自动修复CSS代码(为开发者)、并在修复的基础上压缩代码(为浏览器)的工具,也是很有必要的。
因此,CSSCheckStyle(简称ckstyle)就应运而生了。它自主实现了CSS的解析、检查、修复与压缩,不仅能够检查出CSS的代码风格和取值问题,还能够对CSS代码进行自动修复和压缩,给出符合规范的“开发者视图”代码和高效率的“浏览器视图”代码。此外,它充分利用插件的特性,可以方便灵活的实现功能扩展。
目前此工具已经在github上开源,欢迎大家围观,并加入我们:
为了避免重复造轮子,我们先分析一下现有的一些CSS相关的自动化工具,这里主要分析以下三个工具:
YUICompressor是基于Java的CSS代码压缩工具,主要实现原理是:
下面是YUICompressor源代码的部分截图
由于YUICompressor的这种实现方式,导致了它必然存在一些缺陷,例如:
1
2 3 4 5 6 7 8 |
.test {
margin-top: 10px; margin-right: 10px; margin-bottom: 10px; margin-left: 10px; } ==> .test {margin:10px} |
CSSLint是用JavaScript实现的,用于检查CSS取值和潜在问题的工具,他使用了Nicholas大神的parser-lib作为CSS解析器,并按照parser-lib给出的API来编写检查规则。例如:检查一个规则是否为空的核心代码如下:
分析其源码可以得知,CSSLint的每一个规则都是通过监听parser给出的事件来进行相应的判断:
一旦规则结束并且没有统计到任何property,则说明规则为空。
CSSLint很好的利用了插件机制,实现了检查规则的灵活扩展,构建了一个良性的规则开发和运行生态,但是它也存在一些不足:
1
2 3 4 5 6 7 |
.test1 ul li a {
width:10px; color:#ffffff; -webkit-border-radius:3px; -moz-border-radius : 3px; border-radius:3px } |
实际上,依据《人人FED CSS编码规范》,上述代码问题还非常多。
一个CSS规则中的属性编写顺序,会影响到浏览器的渲染效率,推荐按照“显示属性、盒模型、文本属性、其他属性”的顺序来编写。
在编写CSS时手动调整顺序显得过于繁琐,因此CSSComb就有了它的用武之地。它能够按照推荐的CSS属性排列顺序,将CSS的所有属性重排。例如:
1
2 3 4 5 6 7 |
.test {
width: 100px; /*盒模型*/ height: 100px; /*盒模型*/ border: none; /*盒模型*/ font-size: 16px; /*文本属性*/ display: none; /*显示属性*/ } |
经过CSSComb的重新排列,将会变成符合推荐顺序的排列方式:
1
2 3 4 5 6 7 |
.test {
display: none; /*显示属性*/ width: 100px; /*盒模型*/ height: 100px; /*盒模型*/ border: none; /*盒模型*/ font-size: 16px; /*文本属性*/ } |
值得一提的是,CSSComb提供了大量的编辑器扩展,通过简单的编辑器命令或操作,即可实现属性的批量重排,使用起来非常方便。
该工具存在的一些问题如下:
不论是代码检查、属性排序,还是代码压缩,对于前端开发来说,这几个工具都是必须要使用的。但是,通过以上分析,以及下面的总结表格,很容易看出,如果要在前端开发环境中集成这些工具,并且对这些工具进行统一灵活的扩展和完善,成本非常高。
名称 | 功能 | 实现语言 | CSS解析器 |
---|---|---|---|
YUICompressor | CSS代码压缩 | Java | 无 |
CSSLint | CSS代码检查 | JavaScript | parser-lib(in JS) |
CSSComb | CSS属性重排 | PHP | 自带(in PHP) |
综合以上的工具分析结论,CSSCheckStyle需要解决的问题如下:
为什么不用parser-lib,而需要自己开发解析器呢?主要原因如下:
因此,自主研发的CSS解析器,应该具备以下几个方面的特征:
CSSCheckStyle需要完成的使命光荣而艰巨,并非一朝一夕之功,需要不断收集用户反馈并不断优化完善。
为了把修改和完善的成本降到最低,CSSCheckStyle采用插件的机制,来扩展工具的功能,每一个插件完成一种或一组规则的检查和修复,这样一来,一旦发现某种规则缺失,或者实现过程中出现问题,添加或修改插件代码即可,无需改动整个工具的框架结构。
目前CSSCheckStyle实现的所有检查和修复功能,都是在插件中完成的。
对于代码风格问题的检查,目前并没有比较好的检查工具,因此为了确保规范的严格执行,此功能必须包含。
由于CSS解析过程中,保留了代码的原始信息,因此代码风格问题的检查就比较顺利了。
一般情况下,如果能够检查出一种代码问题,就意味着知道这个问题应该如何修复。在CSSCheckStyle的自动修复实现过程中,大多数问题是可以自动修复的,比如:将#FFFFFF替换为#FFF,但是也有很多问题是无法自动修复的,比如:在CSS代码中使用了!important,如果自动修复这个问题,将影响最终的功能实现。
因此,自动修复分为以下两种情况:
修复不压缩,说明最终生成的是“开发者视图”的CSS,此时,如果遇到不能自动修复的问题,则自动为该代码生成一个特殊注释:/*TOFIX*/,标识此处无法自动修复,请人工修复。
修复且压缩,意味着最终生成的是“浏览器视图”的CSS,在这种视图中,将按照所有已知的压缩规则,最大限度的压缩CSS代码,因此不会生成特殊注释,而是对无法自动修复的问题采取“鸵鸟策略”,对此问题“置之不理”。
只有代码做到了足够的精简、规范,才能最大限度的实现压缩。
下面是一个典型的示例:
针对以上代码:
通过一系列规则的自动修复,最终达到上图右下角的合并和压缩效果。可以看出,只有代码足够精简、规范,才能最大限度的实现压缩。
CSSCheckStyle的处理流程如下图所示:
说明:
具体细节将会在后续的章节中进行详细的描述。
相比于JavaScript或Java等语言的复杂语法,CSS的编写方式相对来说比较固定,解析器实现起来也相对简单。因此,本工具实现了一个简单的CSS解析器。
一般情况下,CSS的整体解析和处理流程如下图所示:
在解析完成之后,StyleSheet/RuleSet/Rule等实体类中保存了CSS解析过程中的原始代码和清理后的代码两部分信息,前者用于检查代码风格,后者用于检查取值。
三种实体类的类图及其关系简图如下所示:
每一个实体类中都包含compress和fixed两个方法,前者用于生成压缩后的代码,后者用于生成修复后的代码。
为了最大限度的实现灵活扩展和可插拔,本工具采用插件机制来加载需要的规则检查类,每一个插件类对应一种或一组检查规则,同时,每一个插件类包含check和fix方法,分别用于检查和自动修复。
插件类的结构和继承关系简图如下所示:
CSSCheckStyle的所有与CSS检查、修复有关的功能,都将通过插件的形式来完成。插件类PluginClass有以下几个约束:
以“0后的单位可以省略”的规则为例,该规则对应的插件代码如下:
说明:
只有了解了插件的加载和使用过程,才能更好的理解以上插件开发规范的由来和目的。
按照插件的开发规范,所有的插件类都放置在plugins目录下,且插件类的具体开发内容也必须遵守上述规范。
有了上述规范,就可以通过以下几个步骤,加载插件并使用插件的有关功能:
了解了插件类的结构和插件的加载使用,对于插件的开发就应该有一定的认识了。如果您想为CSSCheckStyle开发一款插件,则您必须:
非常期待能够在plugins目录下看到您的作品。
按照《人人FED CSS编码规范》的要求,目前已有的一些插件及其相关说明如下:
插件ID | 插件类名 | 插件检查的规范 |
---|---|---|
hexadecimal-color | FEDHexColorShouldUpper | 16进制颜色,大写,并且尽量省略 |
no-font-family | FEDCanNotSetFontFamily | 不允许业务代码设置字体 |
combine-into-one | FEDCombineInToOne | 将可以合并的样式设置合并 |
comment-length | FEDCommentLengthLessThan80 | 注释长度不允许超过80个字符 |
css3-with-prefix | FEDCss3PropPrefix | css3前缀相关检查 |
css3-prop-spaces | FEDCss3PropSpaces | css3缩进相关检查 |
no-style-for-simple-selector | FEDDoNotSetStyleForSimpleSelector | 不要为简单选择器设置样式,避免全局覆盖 |
no-style-for-tag | FEDDoNotSetStyleForTagOnly | 不要为html tag设置样式 |
font-unit | FEDFontSizeShouldBePtOrPx | 字体的单位必须使用px或pt |
hack-prop | FEDHackAttributeInCorrectWay | hack属性时的检查 |
hack-ruleset | FEDHackRuleSetInCorrectWay | hack规则时的检查 |
high-perf-selector | FEDHighPerformanceSelector | 针对低性能的选择器的检查 |
multi-line-brace | FEDMultiLineBraces | 代码多行时的括号检查 |
multi-line-selector | FEDMultiLineSelectors | 代码多行时的选择器检查 |
multi-line-space | FEDMultiLineSpaces | 代码多行时的空格检查 |
add-author | FEDMustContainAuthorInfo | 需要在文件中添加作者信息 |
no-alpha-image-loader | FEDNoAlphaImageLoader | 不要使用alphaImageLoader |
no-appearance-word-in-selector | FEDNoAppearanceNameInSelector | 不要在选择器中出现表现相关的词汇 |
no-comment-in-value | FEDNoCommentInValues | 不要在css属性中添加注释 |
no-empty-ruleset | FEDNoEmptyRuleSet | 删除空的规则 |
no-expression | FEDNoExpression | 不要使用非一次性表达式 |
number-in-selector | FEDNoSimpleNumberInSelector | 不要在选择器中使用简单数字1、2、3 |
no-star-in-selector | FEDNoStarInSelector | 不要在选择器中使用星号 |
del-unit-after-zero | FEDNoUnitAfterZero | 删除0后面的单位 |
no-zero-before-dot | FEDNoZeroBeforeDot | 删除0.2前面的0 |
no-border-zero | FEDReplaceBorderZeroWithBorderNone | 用border:none替换border:0 |
no-underline-in-selector | FEDSelectorNoUnderLine | 不要在选择器中使用下划线 |
add-semicolon | FEDSemicolonAfterValue | 为每一个属性后添加分号 |
do-not-use-important | FEDShouldNotUseImportant | 不要使用important |
single-line-brace | FEDSingleLineBraces | 单行的括号检查 |
single-line-selector | FEDSingleLineSelector | 单行的选择器检查 |
single-line-space | FEDSingleLineSpaces | 单行的空格检查 |
keep-in-order | FEDStyleShouldInOrder | 属性应该按照推荐的顺序编写 |
no-chn-font-family | FEDTransChnFontFamilyNameIntoEng | 不要出现中文的字体设置,改用对应的英文 |
unknown-css-prop | FEDUnknownCssNameChecker | 错误的css属性 |
unknown-html-tag | FEDUnknownHTMLTagName | 错误的html tag |
lowercase-prop | FEDUseLowerCaseProp | 属性应该用小写 |
lowercase-selector | FEDUseLowerCaseSelector | 选择器用小写字母 |
single-quotation | FEDUseSingleQuotation | 使用单引号 |
z-index-in-range | FEDZIndexShouldInRange | z-index取值应该符合范围要求 |
目前所有的插件都有检查功能(check方法),有的还没有对应的修复功能(fix方法),待后续进一步完善。
单元测试是开发者能够自行控制的,保证代码正确运行的有效手段。作为开源工具,CSSCheckStyle开发了一套小型的单元测试框架。其单元测试类型主要包括两类:
通过tests/runUnitTests.py来运行所有的单元测试,测试全部通过才是正常情况。
下面对这两种单元测试类型进行详细的说明。
CSS版单元测试,通过编写CSS文件来编写用例和断言,主要用于测试“代码检查”的功能。凡是放在tests/unit目录下的、不是以_开头的CSS文件,都认为是单元测试文件。
每一个单元测试文件包含断言和待测试代码两部分内容:
在检查的过程中,单元测试框架收集@unit-test-expecteds中的断言内容,并且检查剩余的所有CSS中的代码问题,通过断言与实际代码问题的对比,确定功能是否正确。对比规则如下:
以FEDNoUnitAfterZero的单元测试CSS文件为例。其断言为:
1
2 3 4 5 |
@unit-test-expecteds {
1: unit should be removed when meet 0 in ".test-zero-px" 1: zero should be removed when meet 0.xxx in ".test-zero-dot-one-px" 1: unit should be removed when meet 0 in ".test-padding" } |
而待测试的部分代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.test-zero-px {
width: 0px; } .test-zero { width: 0; } .test-zero-dot-one-px { width: 0.1px; } .test-padding { padding: 1px 0px; } .test-padding-start-with-zero { padding: 0 1px 0 1px; } |
断言给出的几种错误,确实在待测试的代码中出现了,不多也不少,也只有这样,整个测试用例才算正确通过了。
这种单元测试开发方式的优点是:
以上几个优点,对于测试代码的编写而言,都是非常舒服惬意的。
CSS版单元测试,主要解决的是代码检查的单元测试,对于代码自动修复、代码压缩等复杂功能的测试,无法很好的总结出一种通用的测试编写模式,因此提供了Python版的单元测试编写方法。
为了简化断言的编写,Python版单元测试提供了类似QUnit的断言API,按照实际情况,目前包括:
凡是放置在tests/unit目录下的,非asserts.py、helper.py、__init__.py的Python文件,都认为是单元测试文件。
每一个单元测试文件的编写规则如下:
下面是一个简单的自动修复属性顺序的测试示例,需要说明的是,目前CSSCheckStyle的所有Python版单元测试目录,都提供了相应的helper来封装需要的额外操作,因此示例中只需要引入helper的所有成员即可。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from helper import *
def doTest(): #doTest必须有 fixer, msg = doFix('.test {width:100px; display:none;}', '') #doFix由helper提供 styleSheet = fixer.getStyleSheet() equal(len(styleSheet.getRuleSets()), 1, 'one ruleset') #断言1 ruleSet = styleSheet.getRuleSets()[0] equal(ruleSet.selector, '.test', 'it is the selector that i need') #断言2 rules = ruleSet.getRules() equal(rules[0].name, 'display', 'first element is display now') #断言3 equal(rules[1].name, 'width', 'second element is width') #断言4 equal(rules[0].value, 'none', 'first element value is ok') #断言5 equal(rules[1].value, '100px', 'second element value is ok') #断言6 |
上面的测试,自动修复了一段CSS程序,并且通过一系列的断言,保证了:
Python版的单元测试,弥补了CSS版单元测试的不足,其优点是:
综合运用两种单元测试,将使测试代码的编写更加简洁高效。
目前CSSCheckStyle已经加入了setup机制,通过easy_install即可一键安装,并自动生成命令行工具,方便使用。
安装命令为:
easy_install https://github.com/wangjeaf/CSSCheckStyle/archive/master.tar.gz
CSSCheckStyle一共包含三个命令行工具:ckstyle/fixstyle/compress(csscompress),分别用于代码的检查、自动修复、压缩。具体使用方法、命令行参数、配置方式、使用示例,请移步CSSCheckStyle的开源github项目:
https://github.com/wangjeaf/CSSCheckStyle
在CSS编码规范制定完毕但是检查工具效果差强人意的情况下;
在CSS代码风格检查工具缺失导致规范中的代码风格相关规范无法很好保证的情况下;
在CSS工具五花八门各司其职但是实现方式迥异导致工具整合困难使用困难扩展困难的情况下;
在CSS代码在Rule/RuleSet/StyleSheet三个级别持续优化后压缩率能够变得更低CSS效率变得更高而没有工具良好支撑这种优化的情况下;
在CSS应该自动检查发现问题并且自动分情形修复从而开发者能够无后顾之忧的在不影响开发效率的前提下不断提高编码规范程度并优化产出但是没有工具能够良好支持的情况下;
完全遵循(或者说目标是完全遵循)《人人FED CSS编码规范》的CSS解析、检查、修复、压缩的自动化工具CSSCheckStyle应运而生。
目前该工具在检查、修复、压缩等方面都表现出了强劲的生命力和竞争力。未来,我们会在CSSCheckStyle上持续发力,致力于打造一款“多层次全方位立体式”的CSS工具集,希望更多的有识之士能够加入我们,加入到这个工具的建设中来。