anjia / blog

博客,积累与沉淀
106 stars 4 forks source link

CSS 的原生嵌套 #1

Open anjia opened 6 years ago

anjia commented 6 years ago

CSS 的原生嵌套

嗨,你好,我叫安佳,是 360 搜索事业部的一名前端开发工程师。今年5月,我有幸加入了 W3C 的 CSS 工作组,成为其中的一员。第一次参与工作组讨论的是关于 [css-grid-2] Allow minmax where max wins over min,很开心自己的提议被标准采纳了,预计会在 CSS Grid Layout Module Level 2 里实现。

期间,看到了一个关于要 CSS 支持原生嵌套的议题 [css-nesting] request to pick up the css-nesting proposal。当时觉得这个特性很好,于是就表达了自己的观点。一周之后,CSS 工作组在周会上对它进行了讨论,但是打的标签依然是 unknown/future spec。又过了两周,看起来并没有要在下一个模块里实现此特性的计划,也没有要商讨的安排。

这么有用的嵌套功能和scope特性,为什么一直坐在冷板凳上呢?于是,我就想探究下原因。这篇文章就是研读此 Issue 及相关规范的成果,主要有这三部分:

  1. 来自 Web 开发者的呼声:介绍此 Issue 的相关背景
  2. CSS 工作组都干了什么:介绍工作组的工作内容
  3. 未来的原生嵌套:介绍嵌套语法

来自 Web 开发者的呼声

2012年4月13日,CodePen 的联合创始人 Chris Coyier 抱怨 CSS 的类名不支持命名空间,导致要写好多重复的选择器。

2016年2月2日,微软的项目经理 Kenneth Auchenberg 说如果 CSS 支持了变量和嵌套,他将不再使用预处理器。

2016年12月8日,《CSS揭秘》的作者 Lea Verou 调研了使用 CSS 预处理器的首要原因(单选题),有 1838 个人参与了投票,最终并列第一的两个理由是嵌套和变量。她觉得是时候该重新考虑 CSS 原生嵌套的问题了。

2017年7月13日,集设计和开发才能于一身的 UI/UX 自由工作者 Sara Soueidan 说嵌套是她最想要的 CSS 功能。

2017年8月15日,node-inspect 的作者 Jan Olaf Kremscssnext 把嵌套定义成了“明天的 CSS”,但他还是想看到原生的 CSS 嵌套,毕竟 JS 的生态系统已经证明避免“每个人都使用自己的半标准语言”绝对是健康的。

2018年2月23日,Lea Verou 再次发声,说她现在还在用 CSS 预处理器写嵌套,一旦 CSS 支持了原生嵌套,她就果断弃用预处理。

2018年5月25日,postcss-preset-env 的作者 Jonathan Neal 再次提议重新考虑下让 CSS 支持原生嵌套(也就是本文章的切入点),这引来了一波热议。

CSS 工作组都干了什么

其实,早在2014年4月3日,W3C 就发布过一个 CSS范围(scoping)模块 的工作草案;2015年9月23日,谷歌的工程师 Tab Atkins 也发布过一个 CSS嵌套模块 的编辑草案。那个时候,CSS 工作组也讨论过嵌套的问题,但并未通过社区的同意(见会议纪要)。

针对 Jonathan Neal 这次的提议,CSS 工作组的讨论流程如下:

图1. CSS工作组的讨论流程

  1. 要支持原生嵌套
    • 嵌套的样式规则是一个普遍的诉求
    • 现存的 CSS 预处理器都支持写嵌套,且它是最受欢迎的功能之一
    • 有了原生嵌套,就可以不用预处理器了
  2. 决定仅增加嵌套语法糖
    • 开发人员已经习惯了预处理器中的嵌套,嵌套选择器不应该有特殊的优先级
    • 局部样式是有用的,但scope能否真正满足开发人员的需求还不明朗
    • 嵌套和scope是两个维度的特性,建议先实现已经比较成熟的嵌套
  3. 嵌套语法,详见下节

最终的结论是,新增 CSS 嵌套模块,默认 ED(Editor Draft,编辑草案) 阶段,由 Tab Atkins 担任编辑,并收集相关 Issues,直到该特性成为 FPWD(First Public Working Draft,首个公开工作草案)。

ED Editor Draft 编辑草案
WD Working Draft 工作草案
CR Candidate Recommendation 候选推荐标准
PR Proposed Recommendation 提议推荐标准
REC Recommendation 推荐标准(最稳定的)

关于标准的诞生过程,可查看 World Wide Web Consortium Process Document

未来的原生嵌套

CSS Nesting Module Level 3 里定义了 CSS 嵌套,它新增了一个新的选择器:嵌套选择器 &

a, b {
    & c { color: blue; }
}

/* 等价于 */
a c,
b c { color: blue; }

看了上面的写法,我想肯定有小伙伴要问了:那个前缀 & 能省略不写吗?

对此,草案里的解释是:现有的 CSS 解析都是通过一个单独的前瞻符(lookahead token)来区分各种选择器的,如果新增的嵌套语法不写前缀的话,那一段文本就没法提前知道它到底是一个 CSS 声明还是一个 CSS 选择器了,这会非常不利于浏览器的实现。

前瞻符,诸如:

  • # ID 选择器
  • . 类选择器
  • [] 属性选择器
  • * 通用选择器
  • : 伪类
  • :: 伪元素

css-declaration 图2. CSS 声明

另外,如果省略了 & 也就没法区分 #foo { .bar {} } 到底是复合选择器 #foo.bar 还是组合选择器 #foo .bar 了。

草案里定义了两种嵌套方法:直接嵌套和 @nest 规则

直接嵌套

直接嵌套,即直接以嵌套选择器 & 开头

.foo {
  color: blue;
  & > .bar { color: red; }
}
/* 等价于
   .foo { color: blue; }
   .foo > .bar { color: red; }
 */

.foo {
  color: blue;
  &.bar { color: red; }
}
/* 等价于
   .foo { color: blue; }
   .foo.bar { color: red; }
 */

.foo, .bar {
  color: blue;
  & + .baz, &.qux { color: red; }
}
/* 等价于
   .foo, .bar { color: blue; }
   .foo + .baz,
   .bar + .baz,
   .foo.qux,
   .bar.qux { color: red; }
 */

/**
 * 以下写法都是无效的
 */
.foo {
  color: red;
  .bar { color: blue; }
}
/* 无效原因:没有嵌套选择器 & */

.foo {
  color: red;
  .bar & { color:blue; }
}
/* 无效原因:& 没有在组合选择器的第一位 */

.foo {
  color: red;
  &.bar, .baz { color: blue; }
}
/* 无效原因:列表的第二个选择器里没有嵌套选择器 & */

@nest 规则

.foo {
  color: red;
  @nest & > .bar {
    color: blue;
  }
}
/* 等价于
   .foo { color: red; }
   .foo > .bar { color: blue; }
 */

.foo {
  color: red;
  @nest .parent & {
    color: blue;
  }
}
/* 等价于
   .foo { color: red; }
   .parent .foo { color: blue; }
 */

.foo {
  color: red;
  @nest :not(&) {
    color: blue;
  }
}
/* 等价于
   .foo { color: red; }
   :not(.foo) { color: blue; }
 */

/**
 * 以下写法都是无效的
 */
.foo {
  color: red;
  @nest .bar {
    color: blue;
  }
}
/* 无效原因:没有嵌套选择器 & */

.foo {
  color: red;
  @nest & .bar, .baz {
    color: blue;
  }
}
/* 无效原因:列表里并非所有的选择器都包含嵌套选择器 & */

致谢

在此特别感谢,感谢 @cncuckoo(李松峰老师)对本文提出的大量指导建议,让我学到了很多,尤其是极其严谨的工作态度,以及以读者为出发点的行文思路。

你有想说的吗?

看完本篇文章,你有什么想说的吗?欢迎留言。

当然,你也可以去 CSS 工作组的官方 github 上 w3c/csswg-drafts 提 Issue。若是和嵌套相关的,则 Issue 的标题格式是“[css-nesting]...”;若是和scope相关的,则标题格式是“[css-scoping]...”。诚邀大家提出建设性的意见/建议。

相关规范:

本文章仅代表个人观点,与 CSS 工作组无关。特此说明

INCHMAN1900 commented 6 years ago

css预处理器也会有&这个符号用来识别选择器,但是如果省略的话处理器可以识别成后代选择器,草案中规定必须写&符号,是因为这个对浏览器实现起来会比较麻烦?

yisibl commented 6 years ago

很棒的文章!

看了一下,新的嵌套规范几乎就是复刻了 SCSS 的语法,坚定了我继续用 SCSS 的想法,哈哈哈。

总结一下,为了避免浏览器实现上的语法歧义,主要有两点区别:

  1. 必须使用 & 标识符来区别是否嵌套,不加则会语法报错。
  2. 使用 @nest 配合 & 来引用父元素选择器。(实际开发中用的比较少,会造成代码可读性比较差)。

    SCSS 语法

    .foo {
      color: green;
    
      body .bar & {
        color: blue;
      }
    }

    原生嵌套语法

    .foo {
      color: green;
    
      @nest body .bar & {
        color: blue;
      }
    }

    都等价于

    .foo {
      color: green;
    }
    
    body .bar .foo {
      color: blue;
    }

.foo.qux末尾少了个逗号🐶。

anjia commented 6 years ago

@INCHMAN1900 对,是的。草案里必须写&,更多的是考虑到浏览器的实现,没它就没法“提前识别”对象进而分发给不同的模块来处理了(这点 Tab Atkins 应该有和浏览器开发人员交涉过,见 comment1comment2,以及这里 Nesting Style Rules)。

我也有思考过这个问题,为什么预处理器可以省略&,但原生就不可以。我个人觉得,除了上面提到的解析器的内部策略之外,还有一点,就是:预处理器可以做纯字符串处理,而不需要考虑语义,eg.用{}来识别嵌套就可以。但是原生要支持的话,就不能说先将“嵌套写法”变成“正常写法”再处理,而是直接能识别语义。所以其实我还挺好奇,原生嵌套选择器最后在开发者工具Elements/Styles里会是个什么样的呈现方式。

anjia commented 6 years ago

@yisibl 呀!一丝“女”神回复我了,哈哈。

ps.错误已fixed ^^

INCHMAN1900 commented 6 years ago

@anjia 原生采用的lookahead token实现,加上&就不用对解析引擎做更复杂的处理了。希望能尽快进入标准,可以抛弃scss了:smile:。

我觉得应该会分出来单独的一块区域查看元素的嵌套样式,但是嵌套内部的元素的样式会按照解析之后的显示,这样会更方便,不会打断原来的体验。

anjia commented 6 years ago

@yisibl 嗯,是,对@nest的使用场景有同感。我能想到的一种场景就是:当一个UI组件要用到特定页面的时候,在这个组件内部这么写.parent1 @nest {},以适配不同的上下文。大约是这种“子父,倒着用”。“父子,正着用”的话,可读性是会...很无法想象,挺别扭的。

顺便补充~: 刚再读了下草案里的那段@nest说明,说对它的限制会比直接嵌套少点,也可以明显地标识出它就是个嵌套,再就是嵌套位置灵活 ^^

PS. 用 SCSS 以后好无缝切换 哈哈

berwin commented 6 years ago

佳姐棒棒哒~😄

hex-ci commented 6 years ago

文章非常赞~

我现在也算是 直接写嵌套语法,因为可以让 Nginx 支持 SCSS 解析,所以代码直接写嵌套语法,浏览器就可以直接支持,不需要任何额外的工具或服务。😄

anjia commented 6 years ago

@hex-ci 多谢Hex夸奖 😄

LDWX commented 6 years ago

为什么不直接采用SCSS的语法规则呢,感觉这个跟SCSS比起来并没有什么竞争优势啊。这样的话用户为什么要去习惯新的语法规范呢?

INCHMAN1900 commented 6 years ago

@LDWX CSS Nesting Module Level 3是原生实现,使用完全脱离预处理器,跟SCSS并不是竞争关系。 况且Nesting Module Level 3就目前来看,跟SCSS的语法还是很接近的

yisibl commented 6 years ago

@INCHMAN1900 挖个坑:CSS 处理器会继续普遍使用并长期使用下去。 @LDWX 有优势,最明显一点就是可以减小最终 CSS 文件的大小。

@anjia 现在比较担心的是原生实现嵌套太多层级导致生成文件大小变的失控的问题,这个在预处理器中也存在,不过因为会经历编译的过程,我们会看到最终生成文件的体积。Firefox 的开发者对此提出了这个 issue:https://github.com/w3c/csswg-drafts/issues/2881 引号去掉更佳哦~ image

anjia commented 6 years ago

@yisibl 涨姿势,那个 issue 我看下

meibin08 commented 5 years ago

你好,写的很棒,想授权转载到我的公众号(IT平头哥联盟),分享给大家,是否可以?