Skip to content

CMUI 之 Stylus 编码规范(草案) #1

@cssmagic

Description

@cssmagic

基本概念  

对网站项目来说,所有 Stylus 文件分为两类:

  • 入口文件:被页面直接引用的文件称为入口文件。
  • 模块文件:相对独立的样式代码块可以以模块的方式抽象出来,以便被入口文件或其它模块引用(@import)。

显然,只有入口文件是需要被编译成 CSS 文件的。

CMUI  

可以将 CMUI 各主题的 index.styl 文件视为入口文件,也可以视为模块文件(由众多小模块构成的大模块)。这完全取决于使用者是直接使用编译好的 dist/*.css 文件,还是以引用模块的方式来使用各主题的 index.styl 文件。(百姓网手机站采用的是后一种方式。)

CSS 编码规范  

选择符  

  • [强制] 选择符最末层不应该使用通配选择符(*)。

    Stylint 配置:{universal: 'never'}

    // BAD
    #wrapper
        *
            display block
    
    // GOOD
    #wrapper
        div, p, a, span
            display block

 

  • [强制] 当长度值为零时,应省略单位。

    Stylint 配置:{zeroUnits: 'never'}

  • [强制] z-index 的值应为 10 的倍数。

    Stylint 配置:{zIndexNormalize: 10}

  • [强制] 尽可能精简那些可以自动展开的属性值:

    Stylint 配置:{efficient: 'always'}

    #wrapper
        margin 0 0  // BAD
        margin 0 0 0 0  // BAD
        margin 0  // GOOD
        border-width 0 20px 0 20px  // BAD
        border-width 0 20px  // GOOD
  • [强制] 小数点前的零不省略:

    Stylint 配置:{leadingZero: 'always'}

    #wrapper
        opacity .5  // BAD
        opacity 0.5  // GOOD

声明  

[推荐] 声明块中的各条声明需要以一定的顺序排列,以便快速浏览和定位。推荐顺序如下:

  • 显示方式与布局方式
  • 外边距与内边距
  • 尺寸
  • 文本样式
  • 背景与边框
  • 其它样式
#wrapper
    // display and layout
    display block
    position relative
    z-index 10
    float left
    clear both

    // margin and padding
    margin-top 10px
    padding 10px

    // measurement
    height 100px
    min-width 500px

    // text, font and foreground color
    line-height 1.5
    font-weight 700
    color black

    // background and border
    background silver
    border-right 1px solid green

    // other
    cursor pointer
    animation-name my-anim
    backface-visibility hidden

其它事项  

(关于 CSS 的各种最佳实践,请参阅《CSS 编码技巧 · CSS Secrets》。)

文件系统  

文件命名  

  • [强制] Stylus 文件的扩展名为 .styl
  • [强制] 文件名由小写英文字母和数字组成,且必须以英文字母开头;单词之间以连字符分隔。比如 text-style.styl

字符集  

  • [强制] 所有 Stylus 文件一律采用 UTF-8 字符集,文件无 BOM 头。

    EditorConfig 配置:charset = utf-8

  • [强制] 所有 Stylus 文件内部一律不标记 @charset

    如果页面本身没有采用 UTF-8 字符集,则在引用样式文件时需要在 <link> 标签上注明字符集:

    <link rel="stylesheet" href="cmui.css" charset="utf-8">

模块  

模块的组织  

与 JavaScript 模块类似,每个样式模块对应一个物理文件。模块必须以 mixin 的方式组织,而不是以代码片断的方式组织。比如:

// [module.styl]
// BAD
#wrapper
    color red
    border 1px solid
// [module.styl]
// GOOD
my-mixin()
    color red
    border 1px solid

这意味着在导入模块之后,需要手动调用模块中的 mixin。比如,模块的内容是这样的:

// [module.styl]
my-mixin()
    color red
    border 1px solid

而入口文件是这样调用模块的:

// [entry.styl]
@import './module'

#wrapper
    my-mixin()

或这样的(推荐这种方式,因为这会将模块内的 mixin 导入到局部作用域,可以有效避免同名 mixin 可能引起的冲突):

// [entry.styl]
#wrapper
    @import './module'
    my-mixin()

每个模块可包含一个或多个 mixin,建议只包含一个。不相关的多个 mixin 不应组织到同一个模块中。

模块的导入  

[强制] 仅使用 @import 来导入模块,不使用 @require

模块的路径与文件名需要用引号包住。文件路径总是以 ./ 开头,文件名不需要包含 .styl 扩展名:

@import './modules/ui/index'

代码风格  

大小写  

  • [强制] 所有类型选择符一律小写。
  • [强制] 所有属性名和关键字一律小写。
// BAD
DIV, P, A
    COLOR RED

// GOOD
div, p, a
    color red

代码块  

  • [强制] 每条声明独占一行,行尾不写分号。

    Stylint 配置:{stackedProperties: 'never', semicolons: 'never'}

  • [强制] 属性名与属性值之间无冒号,只留一个空格。

    Stylint 配置:{colons: 'never'}

  • [强制] 声明块(以及其它代码块)采用无花括号的风格,一律采用缩进来表示层级关系。

    Stylint 配置:{brackets: 'never'}

    // BAD
    div {
        border: 1px solid;
        a {
            color: red; 
        }
    }
    
    // GOOD
    div
        border 1px solid
        a
            color red

缩进  

  • [强制] 采用一个 tab。

    Stylint 配置:{indentPref: false, mixed: true}

    EditorConfig 配置:indent_style = tab

换行符  

  • [强制] 换行符采用 LF

    EditorConfig 配置:end_of_line = lf

  • [强制] 文件末尾至少要保留一个换行符。

    EditorConfig 配置:insert_final_newline = true

空格  

  • [强制] 行末不留空格。

    Stylint 配置:{trailingWhitespace: 'never'}

    EditorConfig 配置:trim_trailing_whitespace = true

  • [强制] 括号内侧不加空格。

    Stylint 配置:{parenSpace: 'never'}

  • [强制] Mixin 名(以及函数名)与调用括号之间不加空格。

    // BAD
    my-mixin ( arg )
    
    // GOOD
    my-mixin(arg)
  • [强制] 减号的两侧需要用空格间隔,以便与连字符区分(取负运算符同理):

    #wrapper
        $size = 100px
        margin-left $size-10  // WRONG RESULT
        margin-left ($size - 10)  // GOOD
  • [强制] 逗号在用作分隔符时,其后必须加一个空格:

    Stylint 配置:{commaSpace: 'always'}

    #wrapper
        color rgba(0,0,0,0.5)  // BAD
        color rgba(0, 0, 0, 0.5)  // GOOD

括号  

在需要传入一个值的地方使用表达式时,需要把表达式用括号括起来:

#wrapper
    $size = 100px
    margin-left - $size * 0.5  // BAD
    margin-left (- $size * 0.5)  // GOOD

    padding-left $size / 0.5  // WRONG RESULT
    padding-left ($size / 0.5)  // GOOD

其它字符  

  • [强制] 引号一律使用单引号。

    Stylint 配置:{quotePref: 'single'}

    EditorConfig 配置:quote_type = single

  • [推荐] 写在 url() 函数内的 URL 是不需要包一层引号的:

    #wrapper
        background-image url('http://file.baixing.net/logo.png')  // BAD
        background-image url(http://file.baixing.net/logo.png)  // GOOD

注释  

常规注释  

[推荐] 代码中的常规注释优先选择单行注释// comment),而不是原生 CSS 中的多行注释风格(/* comment */)。

[强制] 单行注释的双斜杠之后空一格。

Stylint 配置:{commentSpace: 'always'}

针对代码块的注释独占一行,写在代码块的顶部;针对某个声明(或选择符)的注释写在声明(或选择符)的右侧,与声明之间用一个 tab 间隔(即 Elastic tabstops 风格)。

// comment to a code block
#wrapper
    color red

    // comment to a code block
    .foo,   // comment to a selector
    .bar
        color green
        border 1px solid    // comment to this declaration

特殊注释  

分隔线是一种特殊的注释,用于把文件划分为多个区段;或者说,它用于把多个代码块分组。分隔线有两种层级,两者的配合使用可以提高代码的组织能力。(尽管分隔线很有用,但我们应该优先通过纵深化的树形结构来体现代码块之间的独立关系。)

区段标记(/** section mark **/)是另一种特殊的注释,用于描述各个区段的名称或作用。

分隔线和区段标记的使用示例如下:

/* ============================================= */
/** icon **/
.icon
    ...
/* --------------------------------------------- */
/** icon size **/
.icon
    ...
    &.large
        ...
    &.small
        ...
/* --------------------------------------------- */
/** icon image **/
...

/* ============================================= */
/** btn **/
...

建议在 IDE 或编辑器中将上述特殊注释设置为可以快速输入的代码片断(比如 WebStorm 中的 Live template)。

选择符  

嵌套  

尽可能利用选择符嵌套,来把代码归纳为树形结构。

// BAD
#wrapper
    border 1px solid
#wrapper a
    color red

// GOOD
#wrapper
    border 1px solid
    a
        color red

父级引用  

除了类、伪类、伪元素、属性选择符等情况之外,父级引用往往是不需要的,建议精简:

#wrapper
    border 1px solid

    // BAD
    & > a
        color red

    // GOOD
    > a
        color red

群组选择符  

单纯由类型选择符所构成的群组选择符可以写在一行;但复杂的群组选择符必须分行:

// BAD
#wrapper
    p.warning, h3.highlight
        color red

// OK
#wrapper
    a, p, span
        color red

当分行时,群组内的多个选择符之间建议总是写上逗号:

// GOOD
#wrapper
    p.warning,
    h3.highlight
        color red

仅当确定不会产生歧义或解析错误时,才可以省略逗号:

// OK
#wrapper
    p.warning
    h3.highlight
        color red

注:以下情况存在歧义或解析错误:

// WRONG RESULT
#wrapper
  div strong  // => div: strong;
  h3.highlight
      color red

// ERROR (ParseError on Stylus v0.x, maybe accepted on v1.x)
#wrapper
  foo > bar
  h3.highlight
      color red

变量  

命名  

[强制] 变量必须以 $ 作为前缀。

除前缀外,变量名由全小写英文字母和数字组成,且前缀后的第一个字符必须是英文字母;单词之间以连字符分隔。比如:$color-bg

Stylint 配置:{prefixVarsWithDollar: 'always'}

定义  

每个变量在定义时应该总是独占一行,禁用内联定义的方式。

// BAD
#wrapper
    height $-h = 20px
    line-height $-h

// GOOD
#wrapper
    $-h = 20px
    height $-h
    line-height $-h

作用域  

变量是有作用域的。

作为公开 API 提供的变量必须放置在全局作用域,即成为全局变量。

  • CMUI 核心层提供的公开的全局变量必须以 $cm- 开头,比如 $cm-color-fg
  • CMUI 主题层提供的公开的全局变量必须以具备模块特征的前缀开头,比如对 Baixing 主题来说,$bx-color-gray 就是个不错的变量名。

非公开的变量应该被限制在一定的作用域内(成为局部变量),且建议使用 $- 前缀:

my-mixin()
    $-var1 = 10px  // limited in a mixin

#wrapper
    $-var2 = 20px  // limited in a selector

如果某个变量不属于公开 API,但由于需要被多个根级代码块共享而不得不暴露到全局作用域,则必须使用 $- 前缀:

$-shared-var = 10px

my-mixin()
    size $-shared-var

another-mixin()
    line-height $-shared-var

Mixin  

命名  

Mixin 名由全小写英文字母和数字组成,且必须以英文字母开头;单词之间以连字符分隔。比如 my-mixin()

定义  

Mixin 内部的选择符不写不必要的父级引用:

// BAD
my-mixin()
    // unnecessary
    &
        color red
    // unnecessary
    & .foo
        color green
    // Note: this parent reference is necessary
    &.bar
        color yellow

// GOOD
my-mixin()
    color red
    .foo
        color green
    &.bar
        color yellow

调用  

Mixin 在调用时必须使用括号。比如:

.foo
    my-mixin()

(注意:“透明 mixin” 不在此列,仍以类似属性声明的方式书写。)

Mixin 在调用时必须位于当前代码块的最顶部:

my-mixin()
    color red

// BAD
.bar
    font-weight bold
    my-mixin()

// GOOD
.foo
    my-mixin()
    font-weight bold

参数  

Mixin 在定义时,其参数必须以 $ 开头。

除前缀外,参数名由英文字母和数字组成,采用小驼峰拼写方式。比如 $myParam

作用域  

Mixin 是有作用域的。

作为公开 API 提供的 mixin 必须暴露到全局作用域,即成为全局 mixin。具体实现方法如下:

// [module.styl]
my-mixin()
    color red
    border 1px solid
// [entry.styl]
@import './module'  // `my-mixin()` will be a global mixin

another-mixin()  // defined as a global mixin
    color green

非公开的(即只在内部使用的)mixin 应该被限制在一定的作用域内(成为局部 mixin),且建议使用 - 前缀:

// [entry.styl]
// BAD
-icon-size($size = 16px)  // leaked to global scope
    size $size

.icon
    -icon-size()
    &.large
        -icon-size(32px)

// GOOD
.icon
    -icon-size($size = 16px)  // defined in local scope
        size $size

    -icon-size()
    &.large
        -icon-size(32px)

如果某个 mixin 不属于公开 API,但由于需要被多个根级代码块共享而不得不暴露到全局作用域,则必须使用 - 前缀。

接口  

如果某个 mixin 的作用等同于某个作为公开 API 存在的类名,则应该与该类名同名,比如用 cmBtn() 对应 .cmBtn

函数  

命名  

(参见 mixin 的命名。)

参数  

(参见 mixin 的参数。)

作用域  

(参见 mixin 的作用域。)

不建议使用的功能  

Extend  

Extend 功能会重新组织代码顺序,可能会导致无法预料的结果。因此禁用此功能,改用 mixin 来实现类似的代码组织功能:

// BAD
.class-a
    font-weight bold
    ...
.class-b
    @extend .class-a
    color green
    ...

// OK
.class-a,
.class-b
    font-weight bold
    ...
.class-b
    color green
    ...

// GOOD
my-mixin()
    font-weight bold
    ...
.class-a
    my-mixin()
.class-b
    my-mixin()
    color green
    ...

在上面的示例中,第一段代码使用了 extend 功能,最终生成的 CSS 代码量较少;第二段代码生成的结果与第一段相同,相当于通过人工预先归并代码的方式来减少冗余度,但不易阅读和维护;第三段代码最为清晰,但生成的代码存在冗余部分。

最终,我们选择第三种方式,因为代码冗余会在 Gzip 阶段消化掉,而代码的可维护性永远是第一位的。

Placeholder  

Placeholder 并不是一个独立的功能,它实际上是 extend 的一种高级应用形式。基于相同的原因,禁用此功能,改用 mixin 来实现类似的代码组织功能:

// BAD
$my-placeholder
    font-weight bold
    ...
.class-a
    @extend $my-placeholder
.class-b
    @extend $my-placeholder
    color green
    ...

// GOOD
my-mixin()
    font-weight bold
    ...
.class-a
    my-mixin()
.class-b
    my-mixin()
    color green
    ...

Block

Block 所能提供的功能是 mixin 的子集,没有额外优点。为降低复杂度,禁用此功能。

// BAD
my-block =
    font-weight bold

#wrapper
    {my-block}

// GOOD
my-mixin()
    font-weight bold

#wrapper
    my-mixin()

CSS Literal  

即 CSS 字面量,由 @css 代码块来定义,其内部代码不会被 Stylus 引擎处理,只会原样输出。实际开发中暂未发现必须使用此功能的场景,为降低复杂度,禁用此功能。

Stylint 配置:{cssLiteral: 'never'}

如果存在大段遗留的 CSS 代码需要整合到 Stylus 文件中,建议先将 CSS 文件转换为 Stylus 文件(以下操作会在当前目录下得到 foo.styl 文件):

$ stylus -C foo.css

属性值引用  

这是 Stylus 标榜的独有功能,但实践中发现它对代码质量并没有帮助。为降低复杂度,禁用此功能。应该总是使用变量来实现类似的功能。

// BAD
#wrapper
    height 20px
    line-height @height

// GOOD
#wrapper
    $-h = 20px
    height $-h
    line-height $-h

其它  

其它 Stylint 配置  

  • {valid: true} - 属性名、属性值、选择符必须是有效值。
  • {mixins: ['...', '...']} - 指定自定义的透明 mixin。
  • {none: false} - 不强制是否使用 none 来关闭边框或描边样式(即 border 0border none 均可)。
  • {noImportant: false} - 不禁用 !important 的使用,因为较底层的样式代码可能需要它。
  • {depthLimit: false} - 不限制选择符的嵌套层数。
  • {duplicates: false, globalDupe: false} - 不检查声明与规则的重复情况。
  • {maxErrors: false, maxWarnings: false} - 输出所有校验结果。
  • {namingConvention: false, namingConventionStrict: false} - Stylint 的变量命名检测功能粒度过大,暂时关闭之。
  • {extendPref: false, placeholder: false, blocks: false} - 已禁用 Extend 和 Block 相关功能,因此关闭这些选项。
  • {sortOrder: false} - 这个选项用于校验声明的顺序,但由于我们很难罗列出所有属性,暂时关闭之。

浏览器前缀  

在源码中不书写任何浏览器前缀,只书写标准的 CSS 属性名或属性值。加前缀的事情由 nib 或 Autoprefixer 来自动完成。

// BAD
.foo
    -webkit-box-sizing border-box
    -moz-box-sizing border-box
    box-sizing border-box

// GOOD
.foo
    box-sizing border-box

压缩  

  • 由于风险太大,收益不高,在 minify 阶段放弃任何形式的 “CSS 代码高级压缩” 功能,包含规则合并、声明去重等等。
  • 静态资源服务器在响应所有 CSS 文件时必须做 Gzip 压缩。

相关阅读


(如有任何意见,请直接回复。)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions