CMUI / doc

Documentation for CMUI project.
5 stars 0 forks source link

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

Open cssmagic opened 10 years ago

cssmagic commented 10 years ago

基本概念  

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

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

CMUI  

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

CSS 编码规范  

选择符  

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

#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》。)

文件系统  

文件命名  

与 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

代码块  

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

#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

其它字符  

[推荐] 代码中的常规注释优先选择单行注释// 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 提供的变量必须放置在全局作用域,即成为全局变量。

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

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 配置  

在源码中不书写任何浏览器前缀,只书写标准的 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

压缩  


相关阅读


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

hax commented 8 years ago

“文件名必须为全小写”未描述多个单词的连接应该用下划线还是连接符(减号)。 下面的例子里文件名有下划线。若使用下划线,且当只有一个mixin时,意味着mixin的命名也变成了下划线?然而mixin的的命名例子都是连接符。另外cmui的例子是用驼峰的。

“声明结尾无分号(这意味着所有每条声明均独立一行)”不如写成“每条声明单独一行,行尾毋需分号”。

“属性名与属性值之间无冒号,只留一个空格”此条我在考虑是否用冒号可以更快的帮助区分选择器?

前面是“当分行时,群组内的多个选择符之间通常可以省略逗号”,下面“建议在群组内的多个选择符之间总是写上逗号”,不清楚。

局部作用域的变量/函数/mixin是否命名需要用特殊前缀?我觉得可能必要性并不是特别高。特殊命名也使得重构(在没有工具帮助的情况下)变得更困难。

cssmagic commented 8 years ago

@hax 谢谢评论。这篇文档最大的问题是把主站相关的内容混进来了。我会单独为主站写一篇。其它问题解释如下:

“文件名必须为全小写”未描述多个单词的连接应该用下划线还是连接符(减号)……

“声明结尾无分号(这意味着所有每条声明均独立一行)”不如写成“每条声明单独一行,行尾毋需分号”。

好,会补充,会改进。

“属性名与属性值之间无冒号,只留一个空格”此条我在考虑是否用冒号可以更快的帮助区分选择器?

写冒号确实可以避免几乎所有歧义,因此 Stylus v1.0 有可能会要求写冒号。但写冒号实在是不爽啊,到时候再更新这里吧。(cc @HerringtonDarkholme)

前面是“当分行时,群组内的多个选择符之间通常可以省略逗号”,下面“建议在群组内的多个选择符之间总是写上逗号”,不清楚。

我心里对这一点确实有些纠结,我再理一下。在主站的 Stylus 规范中我可能会要求总是写逗号。

cssmagic commented 8 years ago

@hax 局部作用域的变量/函数/mixin是否命名需要用特殊前缀?我觉得可能必要性并不是特别高。特殊命名也使得重构(在没有工具帮助的情况下)变得更困难。

为什么会更困难?我觉得应该更方便才对?

hax commented 8 years ago

我指的是如果要调整作用域,你还需要更名。

在没有语言支持的情况下,我们可以用命名约定。但是在本例中,stylus自己有作用域,可能并不需要基于约定。

cssmagic commented 8 years ago

我指的是如果要调整作用域,你还需要更名。

在这个规范的设计中,只作为 API 存在的变量才应该成为全局变量。因此,一个变量是全局变量还是局部变量(或称 “私有变量”),是早已注定的,不存在调整作用域的情况。

在没有语言支持的情况下,我们可以用命名约定。但是在本例中,stylus自己有作用域,可能并不需要基于约定。

Stylus 有块级作用域,但没有模块级的私有变量。$- 前缀主要用来标记 “私有”。

HerringtonDarkholme commented 8 years ago

选择符这一条里可以加上不允许过深嵌套(4层),不允许在逗号分组的选择器下再使用逗号分组(这样会导致编译出来的选择器爆炸)

hax commented 8 years ago

@cssmagic 我觉得不太好武断的说,不会存在本来私有的变量发现需要暴露,或者本来暴露的接口调整为私有的情况。

cssmagic commented 8 years ago

我觉得不太好武断的说,不会存在本来私有的变量发现需要暴露,或者本来暴露的接口调整为私有的情况。

实在想不出例子。为降低复杂度,这种想像不出来的极端情况就只能置之度外了。

即使真的要改,手工改改也是 OK 的,而且如果有前缀的话会很容易改 :smile:


更新:稍微改了一下(改回了最开始的版本),给私有变量和私有 mixin 加 $-- 前缀是推荐做法,不强制;只有在不得不暴露到全局作用域时才强制加前缀。——这样究竟好不好,我会在实践中持续反思。

cssmagic commented 8 years ago

选择符这一条里可以加上不允许过深嵌套(4层),

考虑过这一点,但最终不打算做限制,暂时没有找到充足的限制理由。(甚至,我是鼓励选择符纵深化的。因为越是纵深,代码的目的性就越明确,修改起来就越没有压力。)

不允许在逗号分组的选择器下再使用逗号分组(这样会导致编译出来的选择器爆炸)

同上,不打算做限制。如果真的有必要这样写,也没什么大问题,有 Gzip 呢。

Fishfaceeast commented 8 years ago

"选择符最末层不应该使用通配选择符(*)",这一条我认为不需要硬性要求,可能并不是很方便的能罗列出所有需要匹配的元素,而布局上又要求必须覆盖到所有。如果是最末层用了通配符,也不会像用在body之类根元素上那样,遍历很多很多个子元素。

cssmagic commented 8 years ago

"选择符最末层不应该使用通配选择符(*)",这一条我认为不需要硬性要求,可能并不是很方便的能罗列出所有需要匹配的元素,而布局上又要求必须覆盖到所有。

我的考虑是,绝大多数时候我们在最末层使用 * 完全是想偷懒,实践当中也没有发现无法罗列出所有待匹配元素的情况,所以不想放开。因为一旦使用了 *,即造成代码的未知性(我们无法确定它到底要干嘛),很难修改和维护。所以 CMUI 会严格禁止它,业务代码可以再考虑。

如果是最末层用了通配符,也不会像用在body之类根元素上那样,遍历很多很多个子元素。

禁止它的另一个原因确实是性能因素。(DOM 元素与选择符的匹配过程是从选择符的最右侧开始的,采用这个算法是为了快速失败。而如果我们写的选择符的最右侧是 *,则无法做到快速失败。)关于性能影响有多大,有争议;但在移动端,我还是想把这一点先限制住。

cssmagic commented 8 years ago

优先使用 none 来关闭边框或描边样式

没有找到 border 0border none 的实质区别,实际开发中也没有遇到由这两者的差异引发的问题。为避免不必要的复杂度,可能会把这个规则去掉。

hax commented 8 years ago

border: 0 实际是 border: 0 none currentColorborder: none 实际是 border: medium none currentColor 。所以两者在与其他样式配合时有微妙的差别。

此外在老浏览器(ie6,7)中,border: none 似乎不能去除 button/input 的边框。

cssmagic commented 8 years ago

@hax 是的,就是这些区别。但考虑到这些区别并不影响代码质量,打算把这条规则去掉,开发者爱写啥写啥 :smile:

cssmagic commented 8 years ago

PR CMUI/CMUI#22 已加入对应的 Stylint 配置。

cssmagic commented 7 years ago

Stylint 的 bug 略多,跑了一下 CMUI v2 的代码,很多合法的代码都被报错。

有时间还是要自己写一个 Stylus 的 lint 工具。@lyt9304 😅