求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
 
分享到
CSSCheckStyle——CSS的解析、检查、修复和压缩
 

作者:shoyer,发布于2013-1-14,来源:CSDN

 

前言

《人人FED CSS编码规范》中,对CSS的编写方式进行了严格的约束和说明,但是如何在实际开发过程中,确保代码能够严格遵循规范的要求,是规范制定者必须要考虑的问题。

人为遵守规范,是规范制定的初衷,也是最终目标,但是在规范没有变成习惯之前,人为遵守终归是“不靠谱”的。因此:“无工具,不规范”,有了规范,必定需要配套的工具来确保规范的执行。

说起来也比较奇怪,同样是前端开发的三大语言之一,CSS的检查工具备受冷落,而JS的代码规范检查工具则层出不穷。JSLint/JSHint/gjslint等神器不断涌现,且检查的严格程度令人瞠目结舌,但是CSS的检查工具却寥寥无几,仅有的一些检查工具,也只对CSS的取值、是否符合W3C规范进行了检查,对于代码风格只字不提,而各大互联网公司的开发规范中,对于代码风格是有严格约束的。因此:

一个严格按照规范检查CSS代码风格问题的工具是必不可少的

另外,CSS的检查、修复、排序、合并、压缩等功能,是相互关联的,并不是相互孤立的。比如:只有进行了完整的CSS代码修复与合并,才能最大限度的实现压缩。而目前已有的CSS检查、属性排序、压缩等工具,实现方式五花八门,实现语言也是多种多样,不利于把CSS相关的功能有机的整合在一起,也不利于工具的集成和功能的扩展。因此:

依据完善的开发规范,实现一个自动修复CSS代码(为开发者)、并在修复的基础上压缩代码(为浏览器)的工具,也是很有必要的。

因此,CSSCheckStyle(简称ckstyle)就应运而生了。它自主实现了CSS的解析、检查、修复与压缩,不仅能够检查出CSS的代码风格和取值问题,还能够对CSS代码进行自动修复和压缩,给出符合规范的“开发者视图”代码和高效率的“浏览器视图”代码。此外,它充分利用插件的特性,可以方便灵活的实现功能扩展。

目前此工具已经在github上开源,欢迎大家围观,并加入我们:https://github.com/wangjeaf/CSSCheckStyle

部分已有CSS工具的分析

为了避免重复造轮子,我们先分析一下现有的一些CSS相关的自动化工具,这里主要分析以下三个工具:

  1. CSS代码压缩工具: YUICompressor
  2. CSS代码检查工具: CSSLint
  3. CSS属性重排工具: CSSComb

YUICompressor

YUICompressor是基于Java的CSS代码压缩工具,主要实现原理是:

  1. 将CSS文件读取并解析成字符串
  2. 利用正则表达式,替换该字符串中的“应该被替换”的内容
  3. 替换完成之后的字符串即为CSS压缩后的内容

下面是YUICompressor源代码的部分截图

由于YUICompressor的这种实现方式,导致了它必然存在一些缺陷,例如:

1.代码压缩率有限,不能实现高级的CSS压缩,比如下面所示的规则压缩:

.test {
    margin-top: 10px;
    margin-right: 10px;
    margin-bottom: 10px;
    margin-left: 10px;
 }
 ==>
 .test {margin:10px}

2.由于所有规则都已经在源代码中硬编码,所以除非修改核心源码,否则不能很好的实现功能扩展。

CSSLint

CSSLint是用JavaScript实现的,用于检查CSS取值和潜在问题的工具,他使用了Nicholas大神的parser-lib作为CSS解析器,并按照parser-lib给出的API来编写检查规则。例如:检查一个规则是否为空的核心代码如下:

分析其源码可以得知,CSSLint的每一个规则都是通过监听parser给出的事件来进行相应的判断:

  1. startrule为规则开始
  2. property为找到一个属性时的事件
  3. endrule为一个规则结束

一旦规则结束并且没有统计到任何property,则说明规则为空。

CSSLint很好的利用了插件机制,实现了检查规则的灵活扩展,构建了一个良性的规则开发和运行生态,但是它也存在一些不足:

1.没有针对代码风格的检查,并且缺失部分检查规则。例如:下面的代码在CSSLint检查完成之后认为是没有任何问题的。

.test1 ul li a {
 width:10px;
 color:#ffffff;
     -webkit-border-radius:3px;
 -moz-border-radius : 3px;
 border-radius:3px
 } 

实际上,依据《人人FED CSS编码规范》,上述代码问题还非常多。

2.检查出来的CSS问题,没有实现相应的修复方法,比如width:0px可以简化成width:0。需要开发者根据提示,人工修复。

3.插件的编写略显复杂,插件中的核心操作元素是一系列的事件及其监听函数,而不是需要检查的目标(比如文件、规则、属性)本身。

CSSComb

一个CSS规则中的属性编写顺序,会影响到浏览器的渲染效率,推荐按照“显示属性、盒模型、文本属性、其他属性”的顺序来编写。

在编写CSS时手动调整顺序显得过于繁琐,因此CSSComb就有了它的用武之地。它能够按照推荐的CSS属性排列顺序,将CSS的所有属性重排。例如:

.test {
    width: 100px;    /*盒模型*/
    height: 100px;   /*盒模型*/
    border: none;    /*盒模型*/
    font-size: 16px; /*文本属性*/
    display: none;   /*显示属性*/
 } 

经过CSSComb的重新排列,将会变成符合推荐顺序的排列方式:

.test {
    display: none;   /*显示属性*/
    width: 100px;    /*盒模型*/
    height: 100px;   /*盒模型*/
    border: none;    /*盒模型*/
    font-size: 16px; /*文本属性*/
 } 

值得一提的是,CSSComb提供了大量的编辑器扩展,通过简单的编辑器命令或操作,即可实现属性的批量重排,使用起来非常方便。
该工具存在的一些问题如下:

  1. 与YUICompressor一样,所有功能集于一个文件,不能很好的实现功能扩展(虽然属性排序也不需要太多扩展哈~)
  2. 功能相对来说比较单一(也可以说是专注哈~)

总结

不论是代码检查、属性排序,还是代码压缩,对于前端开发来说,这几个工具都是必须要使用的。但是,通过以上分析,以及下面的总结表格,很容易看出,如果要在前端开发环境中集成这些工具,并且对这些工具进行统一灵活的扩展和完善,成本非常高。

名称
功能
实现语言
CSS解析器
YUICompressor CSS代码压缩 Java
CSSLint CSS代码检查 JavaScript parser-lib(in JS)
CSSComb CSS属性重排 PHP 自带(in PHP)

CSSCheckStyle需要解决的问题

综合以上的工具分析结论,CSSCheckStyle需要解决的问题如下:

自主完成CSS的解析

为什么不用parser-lib,而需要自己开发解析器呢?主要原因如下:

  1. 通过CSSLint的插件编写方式可以看出,parser-lib给出的API,对于编写检查规则来说相对比较复杂。
  2. parser-lib解析过程中,给出的代码风格方面的API不足,导致代码风格检查很难进行。
  3. 目前人人FED的每一位开发者都有Python环境,但是并不一定有Java或nodejs,用Python自主开发解析器,也能减少工具推广过程中的额外配置。

因此,自主研发的CSS解析器,应该具备以下几个方面的特征:

  1. 解析过程中,要保留原始的代码信息,以便进行代码风格的检查。
  2. 能够预留非常方便的API,以供插件规则来检查或修复代码。
  3. 按照StyleSheet/RuleSet/Rule的结构来生成解析内容,便于对规则进行划分,用不同类型的规则来检查不同内容。

建立方便灵活的功能扩展机制

CSSCheckStyle需要完成的使命光荣而艰巨,并非一朝一夕之功,需要不断收集用户反馈并不断优化完善。

为了把修改和完善的成本降到最低,CSSCheckStyle采用插件的机制,来扩展工具的功能,每一个插件完成一种或一组规则的检查和修复,这样一来,一旦发现某种规则缺失,或者实现过程中出现问题,添加或修改插件代码即可,无需改动整个工具的框架结构。

目前CSSCheckStyle实现的所有检查和修复功能,都是在插件中完成的。

检查CSS的代码风格问题

对于代码风格问题的检查,目前并没有比较好的检查工具,因此为了确保规范的严格执行,此功能必须包含。

由于CSS解析过程中,保留了代码的原始信息,因此代码风格问题的检查就比较顺利了。

检查并修复CSS的代码问题

一般情况下,如果能够检查出一种代码问题,就意味着知道这个问题应该如何修复。在CSSCheckStyle的自动修复实现过程中,大多数问题是可以自动修复的,比如:将#FFFFFF替换为#FFF,但是也有很多问题是无法自动修复的,比如:在CSS代码中使用了!important,如果自动修复这个问题,将影响最终的功能实现。

因此,自动修复分为以下两种情况:

1. 修复不压缩

修复不压缩,说明最终生成的是“开发者视图”的CSS,此时,如果遇到不能自动修复的问题,则自动为该代码生成一个特殊注释:/*TOFIX*/,标识此处无法自动修复,请人工修复。

2.修复且压缩

修复且压缩,意味着最终生成的是“浏览器视图”的CSS,在这种视图中,将按照所有已知的压缩规则,最大限度的压缩CSS代码,因此不会生成特殊注释,而是对无法自动修复的问题采取“鸵鸟策略”,对此问题“置之不理”。

在修复的基础上压缩代码

只有代码做到了足够的精简、规范,才能最大限度的实现压缩。

下面是一个典型的示例:

针对以上代码:

  1. 只有对每一个Rule采用“0后的单位可以省略”的规则,才能确保所有的margin-*取值一致,便于后续合并。
  2. 只有对每一个RuleSet采用“合并margin-*为margin”的规则,才能确保四个margin-*最终合并成一个。
  3. 只有对每一个RuleSet采用“按照推荐的顺序编写属性”的规则,才能确保display和margin的顺序符合规范,并且为“合并相同属性的规则”提供便利。
  4. 只有对整个StyleSheet采用“合并相同属性的规则”的规则,才能最大限度减少重复代码。

通过一系列规则的自动修复,最终达到上图右下角的合并和压缩效果。

可以看出,

只有代码足够精简、规范,才能最大限度的实现压缩

CSSCheckStyle的处理方案

CSSCheckStyle的处理流程如下图所示:

说明:

  1. CSSParser通过逐字解析CSS文本内容,建立StyleSheet/RuleSet/Rule的对象结构,保存解析后的属性值,并完全保留原始代码的缩进、空格、回车等代码风格信息。
  2. ckstyle Rule Plugin,包含了针对某一种属性级别的代码规范的检查器实现,它的check方法将对每一个Rule进行对应的检查,如果是修复或压缩,则将使用其fix方法进行对应的修复。
  3. 其他两种Plugin与ckstyle Rule Plugin类似,区别在于:ckstyle RuleSet Plugin针对的是规则级别的,而ckstyle StyleSheet Plugin针对的是整个样式表文件级别的。
  4. 任何与规范、检查、修复相关的内容都是一个Plugin,每一个Plugin,都可以包含检查和修复两个部分的功能;代码的压缩和格式化,由每一个StyleSheet/RuleSet/Rule实体来自行承担。
  5. 所有Plugin检查或修复完毕以后,将产生最终的处理结果。如果是检查,则会给出检查结果说明;如果是修复,则会给出修复后并格式化完毕的“开发者视图”代码;如果是压缩,则会在修复后进行最小化输出,给出“浏览器视图”代码。

具体细节将会在后续的章节中进行详细的描述。

CSS的解析

相比于JavaScript或Java等语言的复杂语法,CSS的编写方式相对来说比较固定,解析器实现起来也相对简单。因此,本工具实现了一个简单的CSS解析器。

一般情况下,CSS的整体解析和处理流程如下图所示:

在解析完成之后,StyleSheet/RuleSet/Rule等实体类中保存了CSS解析过程中的原始代码和清理后的代码两部分信息,前者用于检查代码风格,后者用于检查取值。

三种实体类的类图及其关系简图如下所示:

每一个实体类中都包含compress和fixed两个方法,前者用于生成压缩后的代码,后者用于生成修复后的代码。

于插件的检查和修复机制

为了最大限度的实现灵活扩展和可插拔,本工具采用插件机制来加载需要的规则检查类,每一个插件类对应一种或一组检查规则,同时,每一个插件类包含check和fix方法,分别用于检查和自动修复。

插件类的结构和规范

插件类的结构和继承关系简图如下所示:

CSSCheckStyle的所有与CSS检查、修复有关的功能,都将通过插件的形式来完成。插件类PluginClass有以下几个约束:

1.插件类文件必须放置在plugins目录下

2.插件类的类名必须与插件的文件名相同

3.插件类必须按照检查的类型,继承自RuleChecker/RuleSetChecker/StyleSheetChecker当中的一个

4.插件类必须包含id(规则唯一标识)、errorLevel(错误级别)和errorMsg(错误消息)三个属性,id不能与其他插件类重复。

5.插件中可以(并不必须)包含check方法(用于检查代码问题)和fix方法(用于自动修复),其中:

  1. check方法和fix方法拥有相同的参数,第一个为self(Python类实例),第二个为该插件类需要操作的实体类,例如rule/ruleSet/styleSheet,第三个为全局配置对象。
  2. check方法的必须返回:True(检查通过)/ False(检查不通过)/ Array(错误消息数组)。
  3. 为了避免自动修复的相互覆盖,fix方法中统一针对fixedXxxx的属性进行操作,比如Rule实体的fixedName,fixedValue等。

6.errorMsg中可以包含${selector}、${name}、${value},在错误信息解析时将自动替换

以“0后的单位可以省略”的规则为例,该规则对应的插件代码如下:

说明:

1.插件的文件名为:FEDNoUnitAfterZero.py,放置在ckstyle/plugins目录下

2.该文件中包含一个继承自RuleChecker的类,该类名为FEDNoUnitAfterZero

3.该类包含的id为 del-unit-after-zero,errorLevel为WARNING级别,同时包含错误消息设置

4.此插件类中包含了check方法和fix方法,check方法在找到类似于”0px” “0em”的情况下,返回False,否则返回True;fix方法则针对于Rule实体的fixedValue属性,用“0”替换“0px”或“0em”等值,实现修复

插件的加载和使用

只有了解了插件的加载和使用过程,才能更好的理解以上插件开发规范的由来和目的。

按照插件的开发规范,所有的插件类都放置在plugins目录下,且插件类的具体开发内容也必须遵守上述规范。

有了上述规范,就可以通过以下几个步骤,加载插件并使用插件的有关功能:

1.遍历plugins目录下的所有Python文件(Base.py和helper.py除外)

2.根据Python文件名获取文件内的同名类,该类即为插件类

3.按照配置信息(如命令行参数、配置文件参数),构造插件类的实例,并按照类型分类,保存备用

4.当需要检查Rule/RuleSet/StyleSheet时,使用对应插件的check方法来检查代码问题,fix方法来修复代码问题即可,如果插件不存在check或fix方法,则不进行对应操作

插件的开发

了解了插件类的结构和插件的加载使用,对于插件的开发就应该有一定的认识了。如果您想为CSSCheckStyle开发一款插件,则您必须:

1.按照插件类的结构和规范,编写插件类文件

2.把此文件放置在ckstyle/plugins目录下

3.在tests/unit目录下,为您的插件类添加详细的单元测试,单元测试的编写方式请参见“单元测试”小节

非常期待能够在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开发了一套小型的单元测试框架。其单元测试类型主要包括两类:

  1. CSS版单元测试。以一个CSS文件的形式,来编写单元测试的用例和断言
  2. Python版单元测试。在CSS文件形式无法很好满足测试要求的时候,Python版的单元测试可以起到很好的补充作用

通过tests/runUnitTests.py来运行所有的单元测试,测试全部通过才是正常情况。

下面对这两种单元测试类型进行详细的说明。

CSS版单元测试

CSS版单元测试,通过编写CSS文件来编写用例和断言,主要用于测试“代码检查”的功能。凡是放在tests/unit目录下的、不是以_开头的CSS文件,都认为是单元测试文件。

每一个单元测试文件包含断言和待测试代码两部分内容:

  1. 通过@unit-test-expecteds来定义的断言,断言的key为错误优先级,value为错误信息。
  2. 按照普通CSS的书写风格来编写的待检查的CSS,所有错误信息都将被收集。

在检查的过程中,单元测试框架收集@unit-test-expecteds中的断言内容,并且检查剩余的所有CSS中的代码问题,通过断言与实际代码问题的对比,确定功能是否正确。对比规则如下:

  1. 如果断言中存在,而实际检查不存在的问题,则给出[expect but not have]的方式给出错误提示。
  2. 如果断言中不存在,而实际检查存在的问题,则给出[unexpect but has]的方式给出错误提示。

以FEDNoUnitAfterZero的单元测试CSS文件为例。其断言为:

@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"
 } 

而待测试的部分代码如下:

.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;
 } 

断言给出的几种错误,确实在待测试的代码中出现了,不多也不少,也只有这样,整个测试用例才算正确通过了。

这种单元测试开发方式的优点是:

  1. 断言和待测试代码放在同一个文件中,便于快速定位问题
  2. 断言和待测试代码独立开来,避免两种代码揉在一起,相互混淆
  3. 保留了CSS代码的原始编写风格,无需为了测试做任何额外的改变
  4. 如果添加的代码没有任何问题,则只需要编写CSS代码即可,无须改动断言/li>

以上几个优点,对于测试代码的编写而言,都是非常舒服惬意的。

Python版单元测试

CSS版单元测试,主要解决的是代码检查的单元测试,对于代码自动修复、代码压缩等复杂功能的测试,无法很好的总结出一种通用的测试编写模式,因此提供了Python版的单元测试编写方法。

为了简化断言的编写,Python版单元测试提供了类似QUnit的断言API,按照实际情况,目前包括:

  1. ok (flag, msg)
  2. equal (actural, expected, msg)
  3. notEqual (actural, expected, msg)

凡是放置在tests/unit目录下的,非asserts.py、helper.py、__init__.py的Python文件,都认为是单元测试文件。

每一个单元测试文件的编写规则如下:

  1. 必须导入asserts.py,从而能够编写断言,并且收集错误信息
  2. 必须提供doTest方法,并将此方法作为测试的执行方法

下面是一个简单的自动修复属性顺序的测试示例,需要说明的是,目前CSSCheckStyle的所有Python版单元测试目录,都提供了相应的helper来封装需要的额外操作,因此示例中只需要引入helper的所有成员即可。

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程序,并且通过一系列的断言,保证了:

  • 断言1:整个样式表只有一个规则,规则个数正确
  • 断言2:确保唯一的规则的CSS选择器是正确的
  • 断言3:属性重排以后的第一个规则的name为display,属性重排顺序正确
  • 断言4:属性重排以后的第二个规则的name为width,属性重排顺序正确
  • 断言5:属性重排以后的第一个规则的value为none,取值正确
  • 断言6:属性重排以后的第二个规则的value为100px,取值正确

Python版的单元测试,弥补了CSS版单元测试的不足,其优点是:

  1. 能够很好的实现不同类型的测试效果
  2. 通过简单的断言API,测试代码的编写清晰易懂
  3. 无需过多考虑测试框架,编写Python程序放置在对应目录,即可完成测试,方便快捷

综合运用两种单元测试,将使测试代码的编写更加简洁高效。

相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
 
分享到
 
 

相关文章

十天学会DIV+CSS(WEB标准)
HTML 5的革新:结构之美
介绍27款经典的CSS框架
35个有创意的404错误页面
最容易犯的13个JavaScript错误
设计易理解和操作的网站
更多...   

相关培训课程

设计模式原理与应用
从需求过渡到设计
软件设计原理与实践
如何编写高质量代码
单元测试、重构及持续集成
软件开发过程指南

成功案例

东软集团 代码重构
某金融软件服务商 技术文档
中达电通 设计模式原理与实践
法国电信 技术文档编写与管理
西门子 嵌入式设计模式
中新大东方人寿 技术文档编写
更多...   
 
 
 
 
 

深圳SEO优化公司驻马店网站推广工具价格阜阳seo推荐呼和浩特网站优化按天扣费公司中卫优化公司四平seo网站优化多少钱通辽百度网站优化排名哪家好玉溪seo优化公司韶关网站制作设计山南网站改版推荐三亚设计网站推荐武威网站建设价格大同seo排名多少钱楚雄网站优化排名哪家好阜阳seo哪家好同乐百搜标王报价保定SEO按效果付费多少钱张北网站开发佛山seo多少钱盘锦seo网站推广推荐吴忠推广网站报价萍乡高端网站设计哪家好丹竹头优秀网站设计推荐莆田网站优化按天扣费价格宁波阿里店铺运营坪地网站优化软件公司郑州营销网站推荐梧州外贸网站建设价格坪山seo网站优化哪家好乌海seo汕头SEO按天扣费推荐歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化