Sass 模块简介

Avatar of Miriam Suzanne
Miriam Suzanne

DigitalOcean 提供适用于您旅程各个阶段的云产品。 立即开始使用 $200 免费额度!

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.scssforms.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 功能也已迁移到模块系统,因此我们对全局命名空间拥有完全控制权。 有几个内置模块 - mathcolorstringlistmapselectormeta - 必须在使用之前在文件中显式导入。

@use 'sass:math';
$half: math.percentage(1/2);

Sass 模块也可以导入到全局命名空间。

@use 'sass:math' as *;
$half: percentage(1/2);

已经具有前缀名称的内部函数,例如map-getstr-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)是多余且不必要的。 其他函数 - 如lightendarkensaturate等等 - 需要使用更好的内部逻辑重新构建。 原始函数基于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的文件。

默认情况下,所有公共成员都会与模块一起转发。但是,我们可以通过添加showhide子句并命名要包含或排除的特定成员来更具选择性。

// 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 库,例如AccoutrementHerman,这些库仍然使用旧的@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 博客上了解相关信息。

Dart Sass 现在是规范实现,通常会首先实现新功能。如果您想要最新版本,建议您进行切换。您可以使用 Node、Chocolatey 或 Homebrew安装 Dart Sass。它与现有的gulp-sass 构建步骤也非常兼容。

与 CSS(从 CSS3 开始)一样,新版本不再有单个统一的版本号。所有 Sass 实现都使用相同的规范,但每个实现都有独特的发布时间表和编号,在精美的新文档(由Jina 设计)中以支持信息的形式反映出来。

Sass 模块从 **2019 年 10 月 1 日**起在 **Dart Sass 1.23.0** 中可用。