sass 中的 @use 与 @forward

@use

《动态设置网站的主题,顺便狠狠地介绍相关 sass 用法》一文中,定义比主题色稍淡的颜色变量 --primary-color-light 时,我使用到了 mix() 函数,并且说到该函数来自于 sass 为我们提供的内置模块 sass:colormix() 其实是color.mix() 的一个全局别名(global aliases),sass 团队并不建议我们直接使用那些定义在内置模块中的函数的全局别名,并且最终会弃用它们:

The Sass team discourages their use and will eventually deprecate them, but for now they remain available for compatibility with older Sass versions and with LibSass.

按照推荐的用法,我们应该使用 @use 指令导入 sass:color,然后在使用时通过 color.mix() 来使用:

scss 复制代码
@use 'sass:color';
--primary-color-light: #{color.mix(#fff, $color, 10%)};

但是直接这样做的话,在运行项目时可能会遇到如下报错:

解决报错,需要在 nuxt.config.js 中将使用了 @use 的文件 theme.scss,调整到 styleResources.scss 数组的第一位:

有的小伙伴可能要问了:过去,在 sass 中导入其它样式文件通常使用的不都是 @import 吗?这个 @use 是什么?color.mix()color 又是哪里来的?接下来,我们先针对这些疑问,对 @use 做进一步的介绍。

Dart 的不同实现

从 Dart Sass 1.23.0 开始,可以使用 @use 指令来导入样式表,并且这些使用 @use 指令导入的样式表会被认为是模块,默认以文件名作为该模块的命名空间。之所以说是从 Dart Sass 1.23.0 开始可以使用 @use,是因为 dart 有几种不同的实现 ------ 可以分为 Dart Sass,LibSass 和 Ruby Sass。

Ruby Sass 是 sass 的最初实现,但是现在它已经不再受到支持。LibSass 是 sass 在 c/c++ 中的一个实现,要在本地运行,还需要一个 LibSass 的封装,比如 js 封装的 sass.js,node 封装的 node-sass 等,但现在 LibSass 也被弃用了,新项目都应该使用 Dart Sass。

@use@import 的区别

多次导入同一文件

@use@import 一样,也可以导入 .scss 或 .css 文件。比如我们有 _a.scss:

scss 复制代码
$size: 20px;

.a {
  font-size: $size;
}

和 b.css:

css 复制代码
.container {
  color: red;
}

如果我们在 index.scss 中使用 @import 导入 _a.scss 与 b.css,并且还都各自导入 2 次:

scss 复制代码
@import './a';
@import './a';
@import './b';
@import './b';

编译 index.scss 生成的 index.css 结果如下,可以看到导入 2 遍的后果就是文件的内容也被生成 2 遍:

css 复制代码
.a {
  font-size: 20px;
}

.a {
  font-size: 20px;
}

.container {
  color: red;
}

.container {
  color: red;
}

而如果使用的是 @use

scss 复制代码
@use './a';
@use './a';
@use './b';
@use './b';

则在编译时会直接报错:

当我们 @use './a'; 导入 _a.scss 时,sass 会默认以文件名 "a" 作为命名空间(namespace),所以重复的导入会引起报错。解决的办是通过 as 给导入的文件,也就是模块指定别名:

scss 复制代码
@use './a' as a1;
@use './a' as a2;
@use './b' as b1;
@use './b' as b2;

这样成功编译后生成的 index.css 文件中,重复的内容只会生成 1 次:

css 复制代码
.a {
  font-size: 20px;
}

.container {
  color: red;
}

使用导入文件中的变量、函数或混入

对于导入的文件中的变量等内容,在使用时,不同于 @import 时的直接使用,使用 @use 导入时需要在变量等内容前加上导入文件(模块)的命名空间:

scss 复制代码
@use './a';

.box {
  height: a.$size;
}

问号比较多的小伙伴读到这可能又有疑问了:这么写不是更麻烦了,本来直接可以用的东西现在非得多写几个字,有没有什么简便的方法呢?答案是使用 * 来取消别名:

scss 复制代码
@use './a' as *;

.box {
  height: $size;
}

查看 element-plus 的源码,会发现它们在定义样式文件时,很多地方都是这么干的:

私有成员

使用 @use 对比 @import 还有个好处在于可以通过给变量等名称的前面添加 _- 来定义私有成员:

scss 复制代码
$size: 20px;
$_color: blue; // 我是私有的

.a {
  font-size: $size;
  color: $_color; // 内部可用
}

这样在被外部文件导入时,外部文件是用不了 $_color 的:

scss 复制代码
@use './a' as *;

.box {
  height: $size;
  color: $_color; // 报错 Private members can't be accessed from outside their modules.
}

更改变量的默认值

如果某个文件的变量原本有默认值:

scss 复制代码
$size: 20px !default;

.a {
  font-size: $size;
}

在使用 @use 导入时怎么重新赋值呢?方法是通过对导入语句添加 with()

scss 复制代码
@use './a' with($size: 24px);

.box {
  height: a.$size;
}

编译后的 css 文件如下:

css 复制代码
.a {
  font-size: 24px;
}

.box {
  height: 24px;
}

使用 @use 导入文件的一个问题

假设现在有 3 个 sass 文件:在 _b.scss 中定义了变量 $color: yellow;;在 _a.scss 中使用 @import './b'; 导入了 _b.scss;最后又在 index.scss 里 @import './a'; 导入了 _a.scss,那么在 index.scss 中,是可以直接使用变量 $color 的:

编译 index.scss 得到的 index.css 结果为:

css 复制代码
.box {
  color: yellow;
}

但是如果我们将导入文件的指令由 @import 替换成上文中介绍的 @use 后:

在 index.scss 中就无法使用 $color了,无论是通过 a.$color 还是 b.$color 都会报错。为了解决这个问题,就需要用到 @forward 指令。

@forward

我们可以将 _a.scss 中的 @use './b'; 改为 @forward './b';

在 _a.scss 中使用 @forward './b'; 的效果就好像是在 _a.scss 中定义了 $color: yellow; 一般,所以在 index.scss 里使用 $color 时,需要添加上 _a.scss 的命名空间,即 a.$color

@forward 的作用

@forward 导入样式文件时,类似于使用 @use,也会将被导入文件中的内容导入到当前文件中。比如 _b.scss 中除了变量外还定义了样式:

scss 复制代码
$color: yellow;

.b {
  font-size: 12px;
}

我们再把 _a.scss 改为 a.scss 让该文件也被单独编译成 css 文件,可以看到编译结果 a.css 中的内容如下:

css 复制代码
.b {
  font-size: 12px;
}

但是在 a.scss 中,是不能直接使用 _b.scss 里的变量 $color 的。如果要使用,还需要在 a.scss 中写上 @use './b';

scss 复制代码
@forward './b';
@use './b';

.a {
  color: b.$color;
}

读到这里可能有点懵,我再梳理一下,即在 a.scss 中 @forward './b'; 的作用有 2 点:

  1. 在 a.scss 中导入 _b.scss 里定义的样式;
  2. 让通过 @use './a'; 导入了 a.scss 的 index.scss,可以使用 _b.scss 里定义的变量、函数和混入。但是 a.scss 本身不能使用 b.scss 里定义的变量、函数和混入,要使用还得在 a.scss 中写上 @use './b';

另外注意 2 点:

  1. 在 a.scss 中同时 @forward './b';@use './b'; 只会加载一次 _b.scss 模块,而不会有重复的导入;
  2. 最好先 @forward './b';@use './b';。在 sass 文档中有这样一句话:

A stylesheet`s @use rules must come before any rules other than @forward, including style rules.

其中的缘由会在文末说明。

控制可见性

使用 @forward 还可以控制哪些变量、函数或混入是可以被其它文件使用的。比如在 _b.scss 中定义了 2 个变量:

scss 复制代码
$color1: yellow;
$color2: red;

在 _a.scss 中 @forward './b' 时就可以跟上 showhide 来控制 index.scss 中可以使用的变量。比如我选择让 $color1 可见:

scss 复制代码
@forward './b' show $color1;

那么在 index.scss 中就只能使用 a.$color1

scss 复制代码
@use './a';

.box {
  color: a.$color1;
}

如果使用 a.$color2 就会报错:

如果要展示或隐藏多个内容,可以使用 , 分割:

scss 复制代码
@forward './b' show $color1, $color2;

添加前缀

现在假设有 4 个文件,其中 _b.scss 与 _c.scss 都定义了变量 $color,并且都在 _a.scss 通过 @forward 导入:

此时,在 index.scss 中 @use './a'; 后使用 a.$color,就会报错:

解决办法是在 @forward 的时候使用 as 定义前缀:

scss 复制代码
@forward './b' as b-*;
@forward './c' as c-*;

使用时,就在变量名前添加上前缀:

scss 复制代码
@use './a';

.box {
  color: a.$b-color;
}

如果同时需要控制可见性,则书写顺序如下,注意 showhide 的值也要加上前缀:

scss 复制代码
@forward './b' as b-* show $b-color;

更改默认值

阅读 element plus 文档,在关于如何自定义主题的内容中,如果选择使用通过 scss 变量来更换主题色时,有如下一段使用 @forward 的示例:

@forward "<url>" 后还用到了 with,其作用在于重新赋值 url 指向的文件中,被 !default 修饰的变量。比如给 _b.scss 中的 $color 添加上 !default

scss 复制代码
$color: yellow !default;

那么在 _a.scss 中 @forward 时就可以通过 with 重新赋值,注意这里 with 内的 $color 并没有给 color 添加前缀 b-

scss 复制代码
@forward './b' as b-* show $b-color with (
  $color: green
);

这样在 index.scss 里使用:

scss 复制代码
@use './a';

.box {
  color: a.$b-color;
}

编译得到的 index.css 结果如下:

css 复制代码
.box {
  color: green;
}

如果要在 index.scss 中 @use './a' 时重新赋值 _b.scss 中的 $color,则需要在 index.scss 中的 @use './a' 后使用 with,并且此时是需要添加上前缀的,即 $b-color

scss 复制代码
@use './a' with (
  $b-color: purple
);

然后在 _a.scss 中的 with 里的 $color: green 也要添加上 !default

scss 复制代码
@forward './b' as b-* with (
  $color: green !default
);

@forward@use 的先后顺序

在前文中介绍 @forward 的作用时,我说过当使用 @forward@use 加载同一个文件时,最好先 @forward,其中的原因就在于如果已经使用了 @use 加载某一文件,那么就不能再在之后的 @forward 时使用 with 重新赋值变量了。比如下面的写法:

scss 复制代码
// 错误写法
@use './b';
@forward './b' as b-* with (
  $color: green
);

.a {
  color: b.$color; // 这里不要使用前缀写成 b.$b-color
}

就会有如下报错:

正确的写法是先 @forward@use

scss 复制代码
@forward './b' as b-* with (
  $color: green
);
@use './b';

.a {
  color: b.$color;
}

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax