作者简介:
李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。
公众号:1024译站
掘金文章专栏:https://juejin.im/user/57c7cb8a0a2b58006b1b8666/posts
主要分享:Vue.js, JavaScript,CSS
Sass刚刚发布了一个你可能从其他语言中认识的主要新功能:模块系统。这是最常用的Sass功能之一@import向前迈出的一大步。虽然当前的@import规则允许引入第三方包,并将Sass分成可管理的多个“partials”,但它有一些限制:
@import 也是一个CSS特性,两者之间的区别可能会让人感到困惑
如果多次@import相同的文件,它会降低编译速度,导致覆盖冲突,并生成重复的输出。
所有东西都在全局命名空间中,包括第三方包——所以我的color()函数可能会覆盖你现有的color()函数,反之亦然。
当你使用像color()这样的函数时,很难确切地知道它是在哪定义的。它来自哪个@import?
Sass包的作者(像我一样)试图通过手动为变量和函数添加前缀来解决命名空间问题,但是Sass模块是一个更强大的解决方案。简而言之,@import被更明确的@use和@forward规则所取代。在接下来的几年里,Sass@import将被弃用,然后被删除。你仍然可以使用CSS import,但是它们不会被Sass编译。别担心,有一个迁移工具可以帮助你升级!
使用@use导入文件
@use 'buttons';
新的@use 类似于@import。但有一些明显的区别:
该文件只导入一次,不管在项目中@use它多少次。
以下划线(_)或连字符(-)开头的变量、mixin 和函数(Sass称为"成员变量")被认为是私有的,不会被导入。
导入的文件(这里即buttons.scss)中的成员变量只在局部可用,而不会传递到未来的导入结果中。
类似地,@extends将只应用于上游链——即只扩展被导入的文件中的选择器,而不是执行导入命令的文件。
所有导入的成员变量默认拥有命名空间
当我们@use 一个文件时,Sass会根据文件名自动生成一个命名空间:
@use 'buttons'; // 生成了一个buttons
命名空间
@use 'forms'; // 生成了一个 forms
命名空间
我们现在可以同时访问buttons.scss 和forms.scss中的成员变量。但是这两个文件之间并不能互相访问变量。由于导入的特性是带命名空间的,所以我们必须使用一个新的点号分割语法来访问它们:
// variables:
// functions:
form-border: forms.border();
// mixins: @include
@include buttons.submit();
@include forms.input();
我们可以在导入时添加as
@use 'buttons' as *; // 星号会删除所有命名空间
@use 'forms' as 'f';
color; // 不带命名空间的buttons.form-border: f.input-border
使用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';
str-index: string.index('string', 'i');
你可以在Sass模块规范中找到内置模块、函数和名称更改的完整列表
新的和改变的核心功能
作为一个附带好处,这意味着Sass可以安全地添加新的内部mixin和函数,而不会引起命名冲突。这个版本中最激动人心的例子是一个名为load-css()的sass:meta mixin。跟@use 类似 ,但它只返回转换后的CSS输出,我们可以在代码中的任何地方动态使用:
@use 'sass:meta';
$theme-name: 'dark';
[data-theme='#{theme-name);
}
第一个参数是一个模块URL(类似@use),但是它可以被变量动态更改,甚至包括插值表达式,如theme-#{$name}。第二个(可选)参数接受配置值项键值对:
// 在加载之前设置'theme/dark' 中的 with: ('base-color': rebeccapurple)
);
参数 $with接受被加载模块中的任何变量的键和值:
不以_或-开头的全局变量(现在用于表示私有)
标记为 !default 的可配置值
// theme/_dark.scss
_private: true !default; // 不可被配置,因为是私有的
base-color 变量。
还有两个新的sass:meta函数module-variables()和module-functions()。每个函数返回被导入模块中的成员名和值的键值对。它们接受一个与模块命名空间匹配的参数:
@use 'forms';
$form-vars: module-variables('forms');
// (
// button-color: blue,
// input-border: thin,
// )
module参数,允许我们显式地检查每个命名空间。
调整和缩放颜色
sass:color 模块也有一些有趣的注意事项,因为我们试图摆脱一些遗留问题。许多遗留的快捷方式如lighten()或者adjust-hue()现在被弃用,改为显式的 color.adjust() 和color.scale()函数:
// 原来的 lighten(red, 20%)
lightness: 20%);
// 原来的 adjust-hue(red, 180deg)
hue: 180deg);
其中一些旧的函数(如 adjust-hue)变得多余和不必要了。其他的比如lighten, darken,saturate等等,需要用更好的内部逻辑重新构建。原来的函数是基于adjust()的,它利用了线性数学:在上面的例子中,给当前red 变浅20%。在大多数情况下,我们实际上想要相对于当前值缩放(scale())颜色深浅的百分比:
// 跟white相差20%,而不是在当前深浅度上增加20%
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 (
style: 'flat',
);
这类似于load-css()中的开头。
我喜欢这种显式的配置方式,但是有一条规则让我犯了好几次错误:一个模块只能配置一次,即第一次使用它时。导入顺序对Sass来说一直很重要,即便@import也是如此。但这些问题总是静默地失败。现在我们得到一个明确的错误,这是好事,有时也令人惊讶。
确保使用@use时在“入口”文件(导入所有部分的中心文档)中首先配置库,以便在其他地方@use库之前编译这些配置。
(目前来说)不可能在保持可编辑的同时将配置“链接”在一起,但是你可以将已配置的模块与扩展打包,并将其作为新模块传递。
用@forward传递文件
我们并不总是需要使用一个文件,并访问它的成员。有时我们只是想把它传给未来的导入操作。假设我们有多个与表单相关的partials,我们希望将它们全部导入为一个命名空间。我们可以用 @forward来实现:
// forms/_index.scss
@forward 'input';
@forward 'textarea';
@forward 'select';
@forward 'buttons';
被转发的文件的成员在当前文档中不可访问,也没有创建命名空间,但是当另一个文件想要@use 或@forward 整个集合时,这些变量、函数和mixin 就是可访问的。如果转发的部分包含实际的CSS,那么在使用包之前,它也不会生成输出。在这一点上,它将被视为单个模块与单个命名空间:
// styles.scss
@use 'forms'; // 导入forms
命名空间下的所有被转发的成员
注:如果你要求Sass导入一个目录,它会寻找一个名为index或_index的文件)
默认情况下,所有公共成员将使用一个模块进行转发。但我们可以更有选择性地添加show 或hide语句,来包含或排除指定的成员:
// 只转发'input' 中的 border() mixin 和 border-color;
// 转发'buttons' 里的所有成员, gradient() 函数除外
@forward 'buttons' hide gradient;
注意:当函数和mixin共享一个名称时,它们会一起显示和隐藏。
为了区分来源,或避免转发模块之间的命名冲突,我们可以在转发时对模块成员使用as 前缀:
// forms/_index.scss
// @forward "
// 假设两个模块都有一个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
// 只使用该库一次,并带有配置参数
@use 'accoutrement/sass/tools' with (
$font-path: '../fonts/',
);
// 随即转发配置后的库
@forward 'accoutrement/sass/tools';
// 在这里添加其他扩展…
// _anywhere-else.scss
// import the wrapped-and-extended library, already configured
@use 'tools';
@use和@forward必须在(非嵌套的)文档的根部和文件的开头声明。只有@charset和简单的变量定义可以出现在导入指令之前。
维护和编写样式
在网站上使用模块非常有趣。新的语法鼓励我已经在使用的代码结构。我所有的全局配置和工具导入都在一个目录中(我称之为 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库,比如accout和Herman,它们仍然使用旧的@import语法。由于@import规则不会在一夜之间被全部替换,所以Sass提供了一个过渡期。现在就可以使用模块功能,但是@import在一两年后才会被弃用,并且在一年后才会从语言中删除。在此期间,这两个系统将朝两个方向共同努力:
如果我们 @import一个包含新的 @use/@forward 语法的文件,那么只导入公共成员,没有命名空间。
如果我们@use 或@forward一个包含遗留 @import语法的文件,我们将以单个命名空间访问所有嵌套的导入。
这意味着您可以立即开始使用新的模块语法,而不必等待你喜欢的库的新版本:而且我可以花一些时间来更新我的所有库!
迁移工具
如果我们使用Jennifer Thakar构建的Migration Tool,升级应该不会花很长时间。它可以通过 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
sass-migrator module style.scss
默认情况下,迁移工具将只更新单个文件,但在大多数情况下,我们希望更新主文件及其所有依赖项:import、forward或use的任何 partials 。我们可以通过单独指定每个文件,或者添加 --migrate-deps标记来做到这一点:
sass-migrator --migrate-deps module style.scss
对于测试程序,我们可以添加--dry-run --verbose(或简写为 -nv),并在不更改任何文件的情况下查看结果。我们可以使用许多其他选项来定制迁移——甚至有一个专门用于帮助库作者删除旧的手动命名空间——但是我不会在这里全部介绍。在Sass网站上可以找到迁移工具的完整文档。
更新已发布的库
我在库方面遇到了一些问题,特别是试图让用户跨文件访问用户配置,以及处理缺少的链式配置。顺序错误可能很难调试,但是结果表明花这些工夫是值得的,我认为我们很快就会看到一些额外的补丁。我仍然需要在复杂的包上试验迁移工具,并可能为库作者撰写后续文章。
现在要知道的重要的事情是,在过渡期,Sass已经替我们解决了大部分问题。不仅import 和 module 可以一起使用,而且我们可以创建“只有import”的文件,为仍然用 @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 Imports 与 CSS 兼容性 (Dart Sass v1.11)
内容参数与颜色函数 (Dart Sass v1.15)
Dart Sass现在是标准实现,并且通常是第一个实现新特性的。如果你想要最新的,我建议你切换过去。可以使用Node、Chocolatey或Homebrew安装Dart Sass。它还可以与现有的gulp-sass构建步骤一起工作。
跟CSS(从CSS3开始)很像,新的发布不再有一个统一的版本号。所有的Sass实现都遵循相同的规范,但是每个都有一个独特的发布时间表和版本号,这反映在Jina设计的新文档中。
本文已经获得李中凯老师授权转发,其他人若有兴趣转载,请直接联系作者授权。
作者简介:
李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。
公众号:1024译站
掘金文章专栏:https://juejin.im/user/57c7cb8a0a2b58006b1b8666/posts
主要分享:Vue.js, JavaScript,CSS