征求意见稿:模块 系统

2018 年 11 月 27 日发布,作者:Natalie Weizenbaum

Sass 最常用的功能之一是它的导入功能。自 Sass 早期版本发布以来的导入系统,简单来说,并不出色。它仅仅在另一个文件中文本化地包含一个 Sass 文件,这使得难以跟踪混合器、函数和变量的定义位置,也难以确保任何新增内容不会与项目中的其他内容发生冲突。更糟糕的是,它与 CSS 内置的 @import 规则重叠,迫使我们使用 许多启发式算法 来决定哪个是 哪个。

由于这些问题以及其他原因,我们一直希望彻底改造 Sass 文件相互关联的方式,以替代 @import。在过去的几年里,我一直与 Sass 核心团队和 Sass 框架维护者合作,为一个模块系统创建了一个提议,该系统适合替代 @import。这个提议现在已经到了核心团队比较满意的地方,至少作为一个起点,因此我们想把它开放出来,征求社区 反馈。

如果您想阅读完整的提议,它可以在 GitHub 上找到。欢迎您对任何反馈意见 提出问题。提议的主体以规范的形式编写,因此非常详细,但目标、摘要和 FAQ 部分(以下转载)应该对任何熟悉 Sass 的人来说都容易理解。

目标目标永久链接

高层高层永久链接

这些是模块系统作为整体的哲学设计目标。虽然它们没有唯一地指定一个系统,但它们确实代表了许多底层设计 决策背后的根本动机。

  • 局部性。模块系统应该使能够通过仅查看该文件来理解一个 Sass 文件。一个重要的方面是,文件中的名称应该根据文件的內容来解析,而不是根据编译的全局状态来解析。这也适用于创作:作者应该能够确信,只要名称不与文件中可见的任何名称冲突,就可以安全地使用它。 这也适用于创作:作者应该能够确信,只要一个名称不与文件中可见的任何名称发生冲突,那么它就是安全的。

  • 封装。模块系统应该允许作者,尤其是库作者,选择他们要公开的 API。他们应该能够定义供内部使用的实体,而不会让这些实体供外部用户访问或修改。库实现的文件组织应该足够灵活,可以在不改变用户可见的 API 的情况下进行更改。

  • 配置。Sass 在语言中不同寻常,它的设计导致使用文件的唯一目的是产生副作用——具体来说,是发出 CSS。也有一类更广泛的库,它们可能不会直接发出 CSS,但确实定义了用于计算的配置变量,包括计算其他顶层变量的值。模块系统应该允许用户灵活地使用和配置具有 副作用的模块。

底层底层永久链接

这些目标更基于实用性,而不是哲学。在大多数情况下,它们源于我们多年来收集到的关于 @import 的用户反馈。

  • 一次导入。因为 @import 是一个字面上的文本包含,在编译范围内对同一个 Sass 文件进行多次 @import 会编译和运行该文件多次。充其量,这会降低编译速度,而不会带来什么好处,而且当样式本身被复制时,它还会导致 CSS 输出膨胀。新的模块系统应该只编译文件 一次。

  • 向后兼容性。我们希望让人们尽可能容易地迁移到新的模块系统,这意味着要让它与使用 @import 的现有样式表协同工作。只使用 @import 的现有样式表应该具有与 Sass 早期版本相同的导入行为,样式表应该能够将部分更改为 @use,而无需一次更改全部内容。

非目标非目标永久链接

这些是我们明确决定不追求的潜在目标,因为它们是该提议的一部分,原因多种多样。其中一些可能在未来的工作中考虑,但我们认为它们不会阻碍模块 系统。

  • 动态导入。允许通过包含变量或将其包含在条件块中来动态定义模块的路径,这会偏离声明式。除了让样式表更难阅读之外,这还会使任何类型的静态分析更加困难(实际上在一般情况下是不可能的)。它还限制了未来实现 优化的可能性。

  • 一次导入多个文件。除了长期以来一直不支持这个功能的原因——它会让作者容易受到隐蔽且难以调试的顺序错误的影响——这违反了局部性原则,因为它模糊了导入的文件以及名称来自何处的来源。

  • 仅扩展导入。将文件导入,使其生成的 CSS 除非被 @extend,否则不会被发出,这很酷,但也是很多额外的工作。这是最有可能在未来版本中出现的特性,但它并不重要到足以包含在初始模块 系统中。

  • 上下文无关模块。人们倾向于尝试使模块的加载形式,包括它生成的 CSS 以及所有变量的解析值,完全独立于导致其加载的入口点。这将使能够跨多个编译共享加载的模块,甚至有可能将它们序列化到文件系统以进行增量 编译。

    然而,在实践中这是不可行的。生成 CSS 的模块几乎总是基于一些配置,这些配置可能会因不同的入口点而改变,从而使缓存变得毫无用处。更重要的是,多个模块可能依赖于同一个共享模块,一个模块可能在另一个模块使用它之前修改它的配置。在一般情况下禁止这种情况,实际上相当于禁止模块基于 变量生成 CSS

    幸运的是,实现有很大的余地来缓存可以静态确定为上下文无关的信息,包括源树,甚至可能包括常量折叠的变量值和 CSS 树。除了 之外,完全的上下文无关性不太可能提供太多价值。

  • 增加严格性。拥有许多人的大型团队经常希望对 Sass 样式表的编写方式有更严格的规则,以强制执行最佳实践并快速发现错误。人们倾向于使用新的模块系统作为杠杆来进一步推动严格性;例如,我们可以使直接生成 CSS 的部分文件更难,或者我们可以拒绝将我们希望人们避免的函数迁移到新的内置 模块中。

    虽然这很诱人,但我们希望在新的系统中让所有现有用例尽可能容易,即使我们认为它们应该被避免。这个模块系统已经是对现有行为的重大改变,并且需要 Sass 用户进行大量的调整以支持。我们希望让这个过渡尽可能容易,其中一部分是避免添加任何不必要的障碍,用户必须通过这些障碍才能让他们的现有样式表在新的模块 系统中工作。

    一旦 @use 在生态系统中得到广泛采用,我们就可以开始考虑以 lint 或 TypeScript 风格的 --strict-* 标志的形式增加严格性。

  • 代码拆分。能够将单片 CSS 拆分为可以懒加载的单独块对于大型应用程序保持快速加载时间非常重要。然而,这与这个模块系统试图解决的问题是正交的。这个系统主要关注的是对 Sass API(混合器、函数和占位符)进行范围限定,而不是声明生成 CSS 块之间的依赖关系。

    我们相信,这个模块系统可以与外部代码拆分系统协同工作。例如,模块系统可以用来加载用于为单个组件设置样式的库,每个库都被编译成自己的 CSS 文件。这些 CSS 文件然后可以使用特殊的注释或自定义 at-rules 声明对彼此的依赖关系,并由代码拆分 后处理器进行拼接。

摘要摘要永久链接

这个提议添加了两个 at-rules,@use@forward,它们只能出现在样式表的顶层,在任何规则之前(除了 @charset)。它们共同旨在完全替代 @import@import 最终将被弃用,甚至最终从 语言中删除。

@use@use 永久链接

@use 使来自另一个样式表的 CSS、变量、混合器和函数在当前样式表中可用。默认情况下,变量、混合器和函数在一个命名空间中可用,该命名空间基于 URL 的基本名称。

@use "bootstrap";

.element {
  @include bootstrap.float-left;
}

除了命名空间之外,@use@import 之间还有几个重要的区别。

  • @use 只执行样式表并包含其 CSS 一次,无论该样式表被使用多少次。
  • @use 只在当前样式表中使名称可用,而不是 全局可用。
  • 名称以 -_ 开头的成员对于当前样式表来说是私有的,使用 @use
  • 如果样式表包含 @extend,则该扩展只应用于它导入的样式表,而不是导入 它的样式表。

请注意,占位符选择器没有命名空间,但它们确实尊重 隐私。

控制命名空间控制命名空间永久链接

虽然 @use 规则的默认命名空间是由其 URL 的基本名称决定的,但也可以使用 as 显式设置它。

@use "bootstrap" as b;

.element {
  @include b.float-left;
}

特殊的构造 as * 也可以用来将所有内容包含在顶层命名空间中。请注意,如果多个模块公开了具有相同名称的成员,并且使用 as *,Sass 将产生一个 错误。

@use "bootstrap" as *;

.element {
  @include float-left;
}

配置库配置库永久链接

使用 @import,库通常通过设置全局变量来配置,这些变量会覆盖库定义的 !default 变量。因为变量不再是使用 @use 的全局变量,所以它支持一种更明确的库配置方式:with 子句。

// bootstrap.scss
$paragraph-margin-bottom: 1rem !default;

p {
  margin-top: 0;
  margin-bottom: $paragraph-margin-bottom;
}
@use "bootstrap" with (
  $paragraph-margin-bottom: 1.2rem
);

这会将 bootstrap 的 $paragraph-margin-bottom 变量设置为 1.2rem,然后再对其进行评估。with 子句只允许在导入的模块中(或通过它转发)定义的变量,并且只有当这些变量用 !default 定义时才允许,因此用户可以防止 拼写错误。

@forward@forward 永久链接

@forward 规则将另一个模块的变量、mixin 和函数包含在当前模块公开的 API 中,而不会让它们在当前模块内的代码中可见。它允许库作者能够将他们的库拆分成多个源文件,而不会牺牲这些文件中的局部性。与 @use 不同,forward 不会为名称添加任何命名空间。

// bootstrap.scss
@forward "functions";
@forward "variables";
@forward "mixins";

可见性控制可见性控制永久链接

@forward 规则可以选择只显示特定的名称。

@forward "functions" show color-yiq;

它也可以隐藏旨在作为库私有的名称。

@forward "functions" hide assert-ascending;

额外前缀额外前缀永久链接

如果通过一个多合一模块转发子模块,可能需要为该模块添加一些手动命名空间。可以使用 as 子句来实现,它会为转发的每个成员名称添加一个前缀。

// material/_index.scss
@forward "theme" as theme-*;

这样,用户就可以使用多合一模块,其中包含用于主题变量的良好范围内的名称。

@use "material" with ($theme-primary: blue);

或者,他们可以使用子模块,其中包含更简单的名称。

@use "material/theme" with ($primary: blue);

@import 兼容性@import 兼容性永久链接

Sass 生态系统不会一夜之间切换到 @use,因此,它需要与 @import 良好地互操作。这在两个方向上都得到了支持。

  • 当包含 @import 的文件被 @use 时,其全局命名空间中的所有内容都被视为单个模块。然后使用该模块的命名空间来引用该模块的成员,就像正常情况一样。

  • 当包含 @use 的文件被 @import 时,其公共 API 中的所有内容都会添加到导入样式表的全局作用域中。这允许库控制它导出的特定名称,即使对于使用 @import 而不是 @use 的用户也是如此。

为了让库能够维护其现有的以 @import 为导向的 API,并在必要时使用显式命名空间,该提案还增加了对仅对 @import 可见、对 @use 不可见的文件的支持。它们以 "file.import.scss" 形式编写,并在用户编写 @import "file" 时导入。

内置模块内置模块永久链接

新的模块系统还将添加七个内置模块:mathcolorstringlistmapselectormeta。它们将包含所有现有的内置 Sass 函数。由于这些模块(通常)将使用命名空间导入,因此在不与普通 CSS 函数发生冲突的情况下使用 Sass 函数会变得容易得多。

反过来,这将使 Sass 添加新函数更加安全。预计未来会向这些模块添加一些便利函数。

meta.load-css()meta.load-css() 永久链接

该提案还增加了一个新的内置 mixin,meta.load-css($url, $with: ())。此 mixin 动态加载具有给定 URL 的模块,并包含其 CSS(但其函数、变量和 mixin 不会可用)。它替代嵌套导入,有助于解决动态导入的一些用例,而不会产生许多在动态加载新成员时出现的问题。

常见问题解答常见问题解答永久链接

  • 为什么采用这种隐私模型?我们考虑了许多模型来声明成员为私有,包括类似 JS 的模型(其中,只有从模块中显式导出的成员才是可见的),以及类似 C# 的模型(使用显式的 @private 关键字)。但是,这些模型涉及更多样板代码,而且它们在占位符选择器中效果特别差,因为在单个样式规则中可能混合了隐私。基于名称的隐私还提供了一定程度的兼容性,这与库已经使用的约定相符。

  • 如何将成员设为库私有?语言级没有“库”的概念,因此库级隐私也没有内置。但是,一个模块使用的成员不会自动对下游模块可见。如果一个模块没有通过库的主样式表进行 @forward,它就不会对下游消费者可见,因此实际上是库私有的。

    作为一种约定,建议将库私有样式表(这些样式表并非旨在直接由库用户使用)写入名为 src 的目录中。

  • 如何使我的库可配置?如果有一个大型库,由许多源文件组成,这些源文件都共享一些基于 !default 的核心配置,建议将该配置定义在一个文件中,该文件从库的入口点转发,并由库的文件使用。例如

// bootstrap.scss
@forward "variables";
@use "reboot";
// _variables.scss
$paragraph-margin-bottom: 1rem !default;
// _reboot.scss
@use "variables" as *;

p {
  margin-top: 0;
  margin-bottom: $paragraph-margin-bottom;
}
// User's stylesheet
@use "bootstrap" with (
  $paragraph-margin-bottom: 1.2rem
);

发送反馈发送反馈永久链接

这仅仅是一个提案。我们对模块系统的整体形状非常满意,但它还没有最终确定,任何东西都可以随着用户的反馈而改变。如果您有任何意见,请 在 GitHub 上提交问题,或直接 在 Twitter 上发布推文给 @SassCSS。我们很乐意接受从“看起来很棒”到“看起来很糟糕”的任何意见,尽管您越具体,我们获得的信息就越多,我们就能做得越好!