Sass 提案解析:At-Rule 名称中的插值(Interpolation in At-Rule Names)
2026/9/21 16:35:02 网站建设 项目流程

Sass 提案解析:At-Rule 名称中的插值(Interpolation in At-Rule Names)

【免费下载链接】sassSass makes CSS fun!项目地址: https://gitcode.com/gh_mirrors/sa/sass

导读

本篇文章基于 Sass 官方提案 accepted/at-rule-interpolation.md(Draft 1,对应 Issue #429),完整解读"在 at-rule(@规则)名称中使用#{}插值"这一语法特性的设计动机、文法定义与运行时语义。读完本文,你将掌握:为什么插值名称的 at-rule 一律按未知规则(UnknownAtRule)处理、唯一的例外@keyframes如何被特殊照顾、该特性如何用于动态生成带厂商前缀的 at-rule,以及它与 Sass 现有语法(InterpolatedIdentifierInterpolatedValue)之间的承接关系。

提案背景与目标

在 Sass 中,插值(interpolation)是#{}语法,允许把 SassScript 表达式的求值结果拼接到标识符、字符串、选择器等文本片段中。此前,插值可以出现在 at-rule 的值(value)部分,但 at-rule 的名称本身(即@之后的标识符)是固定的字面量。

本提案的目标是:允许任何 at-rule 在其名称中包含插值,例如:

@m#{$vendor}media { ... }

其核心应用场景是动态生成带厂商前缀的 at-rule 名称。由于浏览器厂商常通过-webkit--moz--ms--o-等前缀扩展 CSS 特性,开发者希望用一段 Sass 代码统一输出多个前缀版本的规则,例如:

$prefixes: "-webkit-", "-moz-", ""; @each $prefix in $prefixes { @#{$prefix}keyframes fade-in { ... } }

设计决策:插值名称统一按"未知 at-rule"处理

提案在 Design Decisions 一节明确了关键取舍:

任何 at-rule 都可以在名称中包含插值。插值会自动导致它被解析为未知 at-rule(unknown at-rule),即使插值解析后的名称是 Sass 已知的规则。唯一的例外是@keyframes,它仍然允许包含10%之类的 keyframes 选择器子规则。

为什么不重新解析已知规则?

理论上,可以让 Sass 认识的 CSS at-rule(如@media@supports)即使在名称由插值生成时也被特殊对待。但提案明确指出这会带来极大的实现复杂度:

  1. 实现需要具备在运行时重新解析这些 at-rule 值的能力;
  2. 收益不明确:at-rule 插值主要用于添加厂商前缀,而 Sass 有特殊运行时支持的@media@supports恰好不使用厂商前缀,因此没有为此付出复杂度的价值。

运行时行为

当一个 at-rule 被求值时:

  1. 先对其名称求值,产出一个不带引号的字符串(unquoted string),作为生成 at-rule 的名称;
  2. 再检查该生成名称是否是需要特殊运行时处理的 at-rule;
  3. 在运行时触发特殊处理的只有@keyframes@media@supports等规则是在解析期(parse-time)被检测的。

由此得出一个直接推论:@m#{ed}ia会被当作未知 at-rule,而不会被当作媒体查询规则处理——因为@media的特殊语义绑定在解析期,而不是运行时。

语法:UnknownAtRule 产物的替换

本提案定义了UnknownAtRule产物的替换文法,与现有语法(见 spec/at-rules/unknown.md)相比,唯一区别是名称部分可以使用插值

UnknownAtRule ::= '@' InterpolatedIdentifier InterpolatedValue? ('{' Statements '}')?

文法要点:

  • @之后不允许出现空白
  • 与所有语句一致,不带块(block)的 UnknownAtRule 必须用分号与其他语句分隔
  • InterpolatedIdentifierInterpolatedValue均为可插值产物(详见下文源码佐证)。

与现有 UnknownAtRule 文法的关系

在 spec/at-rules/unknown.md 中,当前规范对未知 at-rule 的文法定义为:

UnknownAtRule ::= '@' InterpolatedIdentifier¹ InterpolatedValue? Block? | `@function'² InterpolatedIdentifier³ InterpolatedValue? Block

(其中 1:名称不得与 Sass 定义的其他 at-rule 重名;2:@function大小写不敏感匹配;3:必须以--开头。)

本提案即是在此基础上,明确"名称可以被插值",并进一步规定插值名称一律走未知规则路径。提案原文特别注明:"这与之前的语法相同,只是 at-rule 的名称可以被插值。"

InterpolatedIdentifier 与 InterpolatedValue

提案中引用的两个非终结符来自 Sass 核心语法(spec/syntax.md):

InterpolatedIdentifier ::= (<ident-token> | '-'? Interpolation) (Name | Interpolation)*

其含义是:一个可插值标识符由一个 CSS ident-token(或可选的-加插值)开头,后跟任意多个 Name 片段或插值组成,各组成部分之间不允许出现空白

InterpolatedValue对应 Sass 的InterpolatedAnyValue,与 CSS 的<any-value>一致,唯一区别是当解析到#{时,会解析一个必须紧跟}的 Expression。此外,spec/syntax.md 中的"Consuming an Interpolated Identifier"算法给出了解析器层面的具体步骤:从-#{#{或普通标识符开始,随后循环消费#{、标识符码点或\转义,最终返回由字符串与表达式组成的组件序列。

运行时语义:求值、拼接与特殊分支

综合提案与 spec/at-rules/unknown.md 的语义描述,一个带插值名称的未知 at-rule 在运行时按以下流程执行:

  1. 求值名称:对InterpolatedIdentifier求值,产出不带引号的字符串name
  2. 求值值:若存在InterpolatedValue,对其求值得到value
  3. 构造 CSS 节点:创建一个名为name、值为value的 CSS 未知 at-rule;
  4. 定位父节点:取当前样式规则(current style rule)、keyframe 块或 at-rule 中最近的作为父节点;
  5. 有 Statements 时
    • 父节点不存在 → 追加到当前模块的 CSS 输出;
    • 父节点是样式规则,且名称是font-face或其去前缀名(unprefixed name)为keyframes→ 追加到当前模块的 CSS 输出;
    • 否则 → 先追加到父节点的父节点,再复制一个不含子节点的父节点副本追加到 css 中(该副本随后成为新的 current style rule);
    • 然后逐个求值其子 Statements;
  6. 无 Statements 时:必要时先拆分当前父节点(splitting the current parent if necessary),再追加到父节点或当前模块的 CSS 输出。

其中关键的一步是第 5 条中的去前缀判断(unprefixed):它引用 spec/syntax.md 对 vendor prefix 的定义——前缀是-开头、后跟一个或多个非-码点、再跟-的子串,且-之后必须还有文本,剩余部分即"无前缀标识符"。

@keyframes:唯一的运行时特殊处理

为什么偏偏是@keyframes?因为它承载的子规则内容是选择器(如0%100%fromto),而这些内容只有在 keyframes 上下文里才合法。当@-webkit-keyframes之类的名称由插值生成后,运行时仍需将其内部内容按 keyframes 选择器解析。

在 spec/style-rules.md 的样式规则语义中可以看到这一分支:

  • 若父节点是名称去掉厂商前缀后为keyframes的未知 at-rule,则子选择器文本会按keyframe selector解析,追加一个 keyframe 块到父节点,并结束当前样式规则的求值;
  • 若父节点是 keyframe 块,则抛出错误(在 keyframes 内不允许再嵌套普通样式规则)。

这一机制保证了@#{$prefix}keyframes生成的@-webkit-keyframes@-moz-keyframes等规则内部仍能正确识别10%from这类 keyframes 选择器。

与 @media、@supports 的对比:解析期 vs 运行时

特性@media/@supports插值名称的未知 at-rule
检测时机解析期(parse-time)运行时(runtime)
特殊运行时处理有(如嵌套媒体查询提升到顶层)无(除@keyframes
厂商前缀不使用主要用途
插值名称结果@m#{ed}ia不被识别为媒体规则一律按 UnknownAtRule 处理

@media为例,Sass 为其提供了特殊支持:媒体查询会被解析两次(第一次在解析 Sass 样式表时允许包含 SassScript 与插值,第二次将求值结果作为纯 CSS 解析),以实现嵌套媒体查询向顶层提升等能力(见 spec/at-rules/media.md)。这些特殊支持全部建立在解析期识别@media的前提之上,因此@m#{ed}ia无法享受这些语义,只能落入未知 at-rule 的通用路径。

综合示例:动态生成厂商前缀规则

结合以上全部语义,一个完整的实战用例是把前缀抽成变量,循环输出多版本 at-rule:

$vendors: ("-webkit-", "-moz-", "-ms-", ""); @each $vendor in $vendors { @#{$vendor}keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } }

编译行为分析:

  • 每次循环,@#{$vendor}keyframes的名称经插值求值,成为不带引号的字符串(如-webkit-keyframes);
  • 由于名称含插值,一律按未知 at-rule 处理;
  • 运行时检查发现其去前缀名(keyframes)命中唯一特殊分支,于是块内的from/to/10%等选择器按 keyframe selector 解析并正确生成 keyframe 块;
  • 最终输出四组独立的@keyframes规则,分别带-webkit--moz--ms-前缀及无前缀版本。

再对比一个反例:若把@media写成@m#{ed}ia,由于媒体规则的特殊语义绑定在解析期,该写法只会被当作一个名为media的未知 at-rule 原样输出,不会触发嵌套媒体查询提升等 Sass 特有行为。

总结

本提案以最小文法改动(UnknownAtRule的名称支持InterpolatedIdentifier)为 Sass 解锁了 at-rule 名称插值能力,同时通过"插值名称一律视为未知 at-rule"的设计,将实现复杂度控制在可接受范围内:唯一的运行时特殊分支给了@keyframes,恰好覆盖了厂商前缀使用场景中最需要动态化的动画规则,而@media@supports这类无需前缀的规则则完全不需要引入昂贵的运行时重解析机制。对于想要输出多前缀 CSS 的工具链与样式库作者而言,这是一个成本极低、收益明确的语法扩展。

延伸阅读(仓库内相关资料)

  • 提案全文:accepted/at-rule-interpolation.md
  • 未知 at-rule 的现行文法与语义:spec/at-rules/unknown.md
  • InterpolatedIdentifier文法与 vendor prefix 定义:spec/syntax.md
  • keyframes 特殊处理的运行时分支:spec/style-rules.md
  • @media的双重解析机制:spec/at-rules/media.md
  • 样式表拆分父节点的过程:spec/stylesheet.md

【免费下载链接】sassSass makes CSS fun!项目地址: https://gitcode.com/gh_mirrors/sa/sass

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询