Sass 刚刚发布了一项重大新功能,您可能在其他语言中也见过:模块系统。 这是对@import
的一个重大进步。 @import
是 Sass 最常用的功能之一。 虽然当前的@import
规则允许您引入第三方包,并将 Sass 拆分为可管理的“部分”,但它也有一些局限性。
@import
也是一项 CSS 功能,其差异可能会令人困惑。- 如果您在多个地方使用
@import
导入同一个文件,它可能会减慢编译速度、导致覆盖冲突以及生成重复的输出。 - 所有内容都在全局命名空间中,包括第三方包 - 所以我的
color()
函数可能会覆盖您现有的color()
函数,反之亦然。 - 当您使用
color()
之类的函数时,您无法准确地知道它是在哪里定义的。 它来自哪个@import
?
Sass 包作者(像我一样)尝试通过手动为我们的变量和函数添加前缀来解决命名空间问题 - 但 Sass 模块是一种更强大的解决方案。 简而言之,@import
正在被更明确的@use
和@forward
规则取代。 在接下来的几年里,Sass @import
将被弃用,然后被移除。 您仍然可以使用CSS 导入,但它们不会被 Sass 编译。 不要担心,有一个迁移工具可以帮助您升级!
使用 @use 导入文件
@use 'buttons';
新的@use
类似于@import
,但有一些显著的差异。
- 无论您在一个项目中使用多少次
@use
,该文件只会被导入一次。 - 以下划线 (
_
) 或连字符 (-
) 开头的变量、混合器和函数(Sass 称为“成员”)被视为私有,不会被导入。 - 来自导入文件的成员(在本例中为
buttons.scss
)只在本地可用,但不会传递给未来的导入。 - 类似地,
@extends
只会向上应用; 扩展导入文件中的选择器,但不会扩展导入此文件的其他文件。 - 所有导入的成员默认情况下都是命名空间。
当我们@use
一个文件时,Sass 会自动根据文件名生成一个命名空间。
@use 'buttons'; // creates a `buttons` namespace
@use 'forms'; // creates a `forms` namespace
我们现在可以访问来自buttons.scss
和forms.scss
的成员 - 但这种访问不会在导入之间传递:forms.scss
仍然无法访问buttons.scss
中定义的变量。 由于导入的特性被命名空间化,我们必须使用新的点分隔语法来访问它们。
// variables: <namespace>.$variable
$btn-color: buttons.$color;
$form-border: forms.$input-border;
// functions: <namespace>.function()
$btn-background: buttons.background();
$form-border: forms.border();
// mixins: @include <namespace>.mixin()
@include buttons.submit();
@include forms.input();
我们可以通过在导入中添加as <name>
来更改或移除默认命名空间。
@use 'buttons' as *; // the star removes any namespace
@use 'forms' as f;
$btn-color: $color; // buttons.$color without a namespace
$form-border: f.$input-border; // forms.$input-border with a custom namespace
使用as *
将模块添加到根命名空间,因此不需要前缀,但这些成员仍然在本地范围内限于当前文档。
导入内置 Sass 模块
内部 Sass 功能也已迁移到模块系统,因此我们对全局命名空间拥有完全控制权。 有几个内置模块 - math
、color
、string
、list
、map
、selector
和meta
- 必须在使用之前在文件中显式导入。
@use 'sass:math';
$half: math.percentage(1/2);
Sass 模块也可以导入到全局命名空间。
@use 'sass:math' as *;
$half: percentage(1/2);
已经具有前缀名称的内部函数,例如map-get
或str-index
,可以在不重复该前缀的情况下使用。
@use 'sass:map';
@use 'sass:string';
$map-get: map.get(('key': 'value'), 'key');
$str-index: string.index('string', 'i');
您可以在Sass 模块规范中找到内置模块、函数和名称更改的完整列表。
新的和更改的核心功能
作为一项附带好处,这意味着 Sass 可以安全地添加新的内部混合器和函数,而不会导致名称冲突。 此版本中最令人兴奋的示例是称为load-css()
的sass:meta
混合器。 这与@use
类似,但它只返回生成的 CSS 输出,并且可以在我们代码中的任何地方动态使用。
@use 'sass:meta';
$theme-name: 'dark';
[data-theme='#{$theme-name}'] {
@include meta.load-css($theme-name);
}
第一个参数是模块 URL(如@use
),但它可以通过变量动态更改,甚至包含插值,如theme-#{$name}
。 第二个(可选)参数接受一个配置值映射。
// Configure the $base-color variable in 'theme/dark' before loading
@include meta.load-css(
'theme/dark',
$with: ('base-color': rebeccapurple)
);
$with
参数接受已加载模块中任何变量的配置键和值,前提是它们同时满足以下条件:
- 一个全局变量,它不以
_
或-
开头(现在用来表示私有)。 - 标记为
!default
值,以便配置。
// theme/_dark.scss
$base-color: black !default; // available for configuration
$_private: true !default; // not available because private
$config: false; // not available because not marked as a !default
请注意,'base-color'
键将设置$base-color
变量。
还有两个新的sass:meta
函数:module-variables()
和module-functions()
。 每个函数都返回一个来自已导入模块的成员名称和值的映射。 它们接受与模块命名空间匹配的单个参数。
@use 'forms';
$form-vars: module-variables('forms');
// (
// button-color: blue,
// input-border: thin,
// )
$form-functions: module-functions('forms');
// (
// background: get-function('background'),
// border: get-function('border'),
// )
其他几个sass:meta
函数 - global-variable-exists()
、function-exists()
、mixin-exists()
和get-function()
- 将获得额外的$module
参数,允许我们显式地检查每个命名空间。
调整和缩放颜色
sass:color
模块也有一些有趣的注意事项,因为我们试图摆脱一些遗留问题。 许多遗留的快捷方式,如lighten()
或adjust-hue()
,现在被弃用,转而支持显式的color.adjust()
和color.scale()
函数。
// previously lighten(red, 20%)
$light-red: color.adjust(red, $lightness: 20%);
// previously adjust-hue(red, 180deg)
$complement: color.adjust(red, $hue: 180deg);
其中一些旧函数(如adjust-hue
)是多余且不必要的。 其他函数 - 如lighten
、darken
、saturate
等等 - 需要使用更好的内部逻辑重新构建。 原始函数基于adjust()
,它使用线性数学:在上面的示例中,将20%
添加到red
的当前亮度。 在大多数情况下,我们实际上希望按百分比scale()
亮度,相对于当前值。
// 20% of the distance to white, rather than current-lightness + 20
$light-red: color.scale(red, $lightness: 20%);
一旦完全弃用并移除,这些快捷方式函数最终将以基于color.scale()
而不是color.adjust()
的新行为重新出现在sass:color
中。 这是分阶段进行的,以避免突然的向后不兼容的更改。 同时,我建议手动检查您的代码,看看在哪些地方color.scale()
可能更适合您。
配置导入的库
第三方或可重复使用的库通常会附带默认的全局配置变量,供您覆盖。 以前我们会在导入之前使用变量来实现这一点。
// _buttons.scss
$color: blue !default;
// old.scss
$color: red;
@import 'buttons';
由于已使用的模块不再能够访问本地变量,我们需要一种新的方法来设置这些默认值。 我们可以通过在@use
中添加一个配置映射来实现。
@use 'buttons' with (
$color: red,
$style: 'flat',
);
这类似于load-css()
中的$with
参数,但不是使用变量名作为键,而是使用变量本身,从$
开始。
我喜欢这种显式配置的方式,但有一条规则让我多次犯错:一个模块只能在第一次使用时被配置一次。 导入顺序对于 Sass 来说一直都很重要,即使是使用@import
也是如此,但这些问题总是默默地失败。 现在我们得到一个明确的错误,既是好事,有时又令人惊讶。 确保在任何“入口点”文件(导入所有部分的中心文档)中首先@use
并配置库,这样这些配置将在其他@use
库之前编译。
在保持可编辑性的同时“链接”配置(目前)是不可能的,但您可以将已配置的模块与扩展一起包装,并将其作为新模块传递。
使用 @forward 传递文件
我们并不总是需要使用文件并访问其成员。 有时我们只想将其传递给未来的导入。 假设我们有多个与表单相关的部分,我们想将它们全部作为单个命名空间导入。 我们可以使用@forward
来实现。
// forms/_index.scss
@forward 'input';
@forward 'textarea';
@forward 'select';
@forward 'buttons';
传递文件的成员在当前文档中不可用,并且不会创建命名空间,但当另一个文件想要@use
或@forward
整个集合时,这些变量、函数和混合器将可用。 如果传递的部分包含实际的 CSS,那么这些 CSS 也会被传递,不会生成输出,直到包被使用。 在那时,它们将被视为具有单个命名空间的单个模块。
// styles.scss
@use 'forms'; // imports all of the forwarded members in the `forms` namespace
注意:如果要求 Sass 导入目录,它将查找名为index
或_index
的文件。
默认情况下,所有公共成员都会与模块一起转发。但是,我们可以通过添加show
或hide
子句并命名要包含或排除的特定成员来更具选择性。
// forward only the 'input' border() mixin, and $border-color variable
@forward 'input' show border, $border-color;
// forward all 'buttons' members *except* the gradient() function
@forward 'buttons' hide gradient;
注意:当函数和mixin共享一个名称时,它们会一起显示和隐藏。
为了澄清源代码或避免转发模块之间的命名冲突,我们可以使用as
作为部分成员的前缀,就像我们转发一样。
// forms/_index.scss
// @forward "<url>" as <prefix>-*;
// assume both modules include a background() mixin
@forward 'input' as input-*;
@forward 'buttons' as btn-*;
// style.scss
@use 'forms';
@include forms.input-background();
@include forms.btn-background();
而且,如果需要,我们始终可以通过添加两个规则来@use
和@forward
同一个模块。
@forward 'forms';
@use 'forms';
这在您想在将库传递到其他文件之前用配置或任何其他工具包装库时特别有用。它甚至可以帮助简化导入路径。
// _tools.scss
// only use the library once, with configuration
@use 'accoutrement/sass/tools' with (
$font-path: '../fonts/',
);
// forward the configured library with this partial
@forward 'accoutrement/sass/tools';
// add any extensions here...
// _anywhere-else.scss
// import the wrapped-and-extended library, already configured
@use 'tools';
@use
和@forward
都必须在文档的根目录(而不是嵌套)中声明,并且必须在文件开头。只有@charset
和简单的变量定义可以出现在导入命令之前。
迁移到模块
为了测试新语法,我构建了一个新的开源 Sass 库(Cascading Color Systems)和一个新乐队网站 - 两者仍在建设中。我想了解模块作为库和网站作者的意义。让我们从使用模块语法编写网站样式的“最终用户”体验开始…
维护和编写样式
在网站上使用模块是一种乐趣。新语法鼓励我使用的代码架构。所有全局配置和工具导入都位于单个目录中(我称之为config
),其中包含一个索引文件,它转发我需要的所有内容。
// config/_index.scss
@forward 'tools';
@forward 'fonts';
@forward 'scale';
@forward 'colors';
当我构建网站的其他方面时,我可以根据需要导入这些工具和配置。
// layout/_banner.scss
@use '../config';
.page-title {
@include config.font-family('header');
}
这甚至适用于我现有的 Sass 库,例如Accoutrement和Herman,这些库仍然使用旧的@import
语法。由于@import
规则不会在一夜之间被完全替换,因此 Sass 建立了一个过渡期。模块现已可用,但@import
将在一年或两年内不会被弃用 - 并且在一年后才会从语言中删除。在此期间,这两个系统将双向协同工作。
- 如果我们
@import
包含新@use
/@forward
语法的文件,则只会导入公共成员,而不会有命名空间。 - 如果我们
@use
或@forward
包含旧式@import
语法的文件,我们将可以访问所有嵌套的导入,作为单个命名空间。
这意味着您可以立即开始使用新的模块语法,而无需等待您喜欢的库的新版本发布:我也可以花一些时间更新所有库!
迁移工具
如果我们使用Jennifer Thakar 构建的迁移工具,升级应该不会花费很长时间。它可以通过 Node、Chocolatey 或 Homebrew 安装。
npm install -g sass-migrator
choco install sass-migrator
brew install sass/sass/migrator
这不是用于迁移到模块的一次性工具。现在 Sass 已恢复活跃开发(见下文),迁移工具也将定期更新,以帮助迁移每个新功能。建议您将其全局安装,并将其保留以备将来使用。
迁移器可以在命令行中运行,并且有望添加到第三方应用程序(如 CodeKit 和 Scout)中。将其指向单个 Sass 文件(如style.scss
),并告诉它要应用哪些迁移。目前只有一个名为module
的迁移。
# sass-migrator <migration> <entrypoint.scss...>
sass-migrator module style.scss
默认情况下,迁移器只会更新单个文件,但在大多数情况下,我们希望更新主文件及其所有依赖项:所有被导入、转发或使用的部分。我们可以通过单独提及每个文件或添加--migrate-deps
标志来实现。
sass-migrator --migrate-deps module style.scss
为了进行测试运行,我们可以添加--dry-run --verbose
(或简写为-nv
),并在不更改任何文件的情况下查看结果。我们可以使用许多其他选项来自定义迁移 - 甚至专门用于帮助库作者删除旧的手动命名空间 - 但我在这里不会介绍所有选项。迁移工具在 Sass 网站上已完整记录。
更新发布的库
我在库方面遇到了一些问题,特别是尝试在多个文件中提供用户配置,并解决缺少链式配置的问题。排序错误可能难以调试,但结果是值得的,我认为我们很快就会看到一些额外的补丁。我仍然需要在复杂包上试验迁移工具,并可能为库作者撰写一篇后续文章。
现在需要知道的最重要的事情是,Sass 在过渡期间为我们提供了保障。不仅导入和模块可以协同工作,而且我们可以创建“仅导入”文件,为仍然@import
我们的库的旧版用户提供更好的体验。在大多数情况下,这将是主包文件的替代版本,您希望它们并排放置:<name>.scss
供模块用户使用,<name>.import.scss
供旧版用户使用。任何时候用户调用@import <name>
,它都会加载该文件的.import
版本。
// load _forms.scss
@use 'forms';
// load _forms.input.scss
@import 'forms';
这在为非模块用户添加前缀时特别有用。
// _forms.import.scss
// Forward the main module, while adding a prefix
@forward "forms" as forms-*;
升级 Sass
您可能还记得,Sass 在几年前曾冻结功能,以使各种实现(LibSass、Node Sass、Dart Sass)都能赶上,并最终淘汰了原始的 Ruby 实现。冻结在去年结束,在 GitHub 上发布了许多新功能以及积极的讨论和开发 - 但没有太多宣传。如果您错过了这些版本,可以在Sass 博客上了解相关信息。
- CSS 导入和 CSS 兼容性(Dart Sass v1.11)
- 内容参数和颜色函数(Dart Sass v1.15)
Dart Sass 现在是规范实现,通常会首先实现新功能。如果您想要最新版本,建议您进行切换。您可以使用 Node、Chocolatey 或 Homebrew安装 Dart Sass。它与现有的gulp-sass 构建步骤也非常兼容。
与 CSS(从 CSS3 开始)一样,新版本不再有单个统一的版本号。所有 Sass 实现都使用相同的规范,但每个实现都有独特的发布时间表和编号,在精美的新文档(由Jina 设计)中以支持信息的形式反映出来。

Sass 模块从 **2019 年 10 月 1 日**起在 **Dart Sass 1.23.0** 中可用。
天啊,我有很多项目需要迁移!至少他们给了我们两年的时间,一年用于弃用,一年用于移除。
完美!我刚开始一个新项目,这次更新来得正是时候。
将在我的新项目中实施所有这些新功能。感谢上帝,我现在才发现这篇文章。
我认为在为命名空间设置别名时,需要省略别名周围的引号。
所以这个
@use 'forms' as 'f';
应该是这个
@use 'forms' as f;
感谢您的文章!我现在正在重构我的当前项目,并且对更新感到很兴奋!
一些非常令人兴奋的功能即将推出。期待它们在 node-sass 中实现。
我是唯一一个对这个重大新功能在社区中如此沉默感到惊讶的人吗?到目前为止只有 5 条评论。在 StackOverflow 或 Reddit 上没有讨论(据我所知)。我订阅的前端新闻通讯中也没有提及…
我需要花一天时间进行实验,才能掌握这些变化的影响。我对大型项目中的实际影响一无所知。虽然我感觉到改善范围界定和避免模块内容泄漏到全局范围的积极意义,但我仍然犹豫不决,不知道这种“JS 思维”是否真的适用于 SCSS/CSS。
除此之外,还有一些边缘主题,比如 Shadow DOM、STYLE[scoped] 和自定义属性,这使得整个情况更加复杂。
随着越来越多的浏览器/操作系统支持暗黑模式,主题化是否会变得更加重要?主题化可能是新模块系统的首要应用——但在 19 年的 Web 前端工作中,我从未遇到过一个客户要求我为他们的网站制作多个主题。
因此,虽然我认识到模块系统背后的思想,但我需要更多的时间和实践才能判断在项目中是否需要以及如何实现它。
我认为这是一个重大消息,值得更多关注,但我不会称之为 JS 思维,也不会将其与 Shadow DOM 和 scoped 样式相关联。这更多地基于 python/nunjucks 模块,并且只与 Sass 特定的元素有关,例如变量、函数和 mixin。它对输出 CSS 没有任何直接影响——只影响我们在 Sass 中如何组合部分代码并共享库。它不是试图管理 DOM 范围、主题或其他“模块化 CSS”问题。这非常像 Sass 语言特性,而不是一种新的 CSS 思路。这可能是它没有引起关注的原因。
我使用 SCSS 越多,就越喜欢 LessJS。
也许这很好,但它仍然让人恼火,因为实现一个项目并不像你所说的那样容易,而且我们又回到了尝试 100 种不同的使用方式、文件夹结构等等,试图让我们的项目以某种方式运行。
唉……
好吧,所以我直接忽略它,继续使用 @import——至少它仍然可以正常工作。
也许以后一切都会变得更加清晰。
天哪,身处行业之外,努力跟上潮流真是太难了 :(
这是一个向好的转变。
有趣的发展——正如其他人所说,这似乎是一个总体上不错的想法,但我确实对过渡过程的直观性有一些疑虑。正如其他人所说,我也很惊讶我听到关于它的消息如此之少,因为它听起来像是重大发展。
我注意到这篇文章没有说明 @import 和当前全局函数的弃用时间线。来自 Sass 版本发布文章
似乎弃用计划经过了深思熟虑,但考虑到依赖 Sass 的各种项目和库,时间安排似乎有点短。
似乎 SASS 模块(使用 @use、@forward 等)在 Angular 8.x 或更高版本中尚未得到支持,即使使用了 dart-sass。
令人沮丧的是,angular 网站或 github 上都没有提到这个问题。完成所有变量和 mixin 的 sass 文件夹结构后,我迎来了这个令人不快的惊喜。
我是 SCSS 新手
我有这个文件
// Theme.scss
$palette-primary-dark: #007daa;
然后导入到另一个文件中
// global.scss
@use “./Theme.scss” // 省略 .scss 会导致错误
.error {
color: theme.$palette-error; // theme.$palette-error 错误提示不存在
}
我无法使用
@use 'Theme'
重现你的错误。这应该可以正常工作,无需使用./
或.scss
,除非你的设置中存在其他不明确的因素。我看到的其他问题变量
$palette-primary-dark
和$palette-error
不匹配,但我假设这是复制粘贴错误命名空间区分大小写,因此你需要使用
Theme.$palette-error
而不是theme.$palette-error
使用 @use 导入编译的 css 字体文件时遇到问题。似乎无法解析文件路径,我认为这与这些文件中使用的 @font-face 规则有关?有人遇到过这种情况吗?或者知道使用 @use 方法导入 @font-face 块的方法吗?
Font-face 不应该引起任何问题。如果路径无法解析,我预计可能是 Sass 导入路径配置存在问题——或者有其他构建工具(webpack?)参与其中,改变了路径解析方式。