Bootstrap SASS 变量覆盖挑战

Bootstrap SASS variable override challenge

编辑: 这个问题被标记为与 this one 重复,以及答案没有回答的内容。

我正在开发一个使用 Bootstrap 的网络应用程序 3. 我有一个基本的 3 层覆盖架构,其中 1) Bootstrap 的 _variables.scss 包含核心变量,2) _app-variables.scss 包含覆盖 Bootstrap 的 _variables.scss 的基本应用程序变量,以及 3) _client-variables.scss 包含覆盖 _app- 的特定于客户端的自定义variables.scss。 #2 或#3(或两者)都可以是空白文件。所以这是覆盖顺序:

_variables.scss // Bootstrap's core
_app-variables.scss // App base
_client-variables.scss // Client-specific

理论上很简单,但问题出现了,因为我称之为 "variable dependencies"——其中变量被定义为其他变量。例如:

$brand: blue;
$text: $brand;

现在,假设上述变量定义在_variables.scss中。然后假设在 _app-variables.scss 中,我仅覆盖 $brand 变量使其变为红色:$brand: red。由于 SASS 按顺序逐行解释代码,它首先将 $brand 设置为蓝色,然后将 $text 设置为蓝色(因为此时 $brand 为蓝色),最后将 $brand 设置为红色的。所以最终结果是,之后更改 $brand 不会影响任何基于 $brand 旧值的变量:

_variables.scss
---------------------
$brand: blue;
$text: $brand; // $text = blue
.
.
.

_app-variables.scss
---------------------
$brand: red; // this does not affect $text, b/c $text was already set to blue above.

但显然这不是我想要的 - 我希望我对 $brand 的更改影响依赖它的所有内容。为了正确覆盖变量,我目前只是将 _variables.scss 的完整副本复制到 _app-variables.scss,然后从那时起在 _app-variables 中进行修改。同样,我将 _app-variables.scss 的完整副本复制到 _client-variables.scss 中,然后在 _client-variables.scss 中进行修改。显然,从维护的角度来看,这不太理想(轻描淡写)——每次我修改 _variables.scss(在 Bootstrap 升级的情况下)或 _app-variables.scss,我必须手动将更改滴入文件覆盖堆栈。而且我不得不重新声明大量我什至可能不会覆盖的变量。

我发现 LESS 有他们所说的 "lazy loading" (http://lesscss.org/features/#variables-feature-lazy-loading),其中变量的最后一个定义在任何地方都被使用,甚至在最后一个定义之前。我相信这会解决我的问题。但是有人知道使用 SASS 的正确变量覆盖解决方案吗?

附录:
这是我已经考虑过的一种技术:以相反的顺序包含文件,对所有变量使用 !defaultthis question 的答案中也建议使用这种技术)。所以结果是这样的:

_app-variables.scss
---------------------
$brand: red !default; // $brand is set to red here, overriding _variables.scss's blue.
.
.
.


_variables.scss
---------------------
$brand: blue !default; // brand already set in _app-variables.scss, so not overridden here.
$text: $brand !default; // $text = red (desired behavior)

因此该解决方案几乎完美。 但是,现在在我的覆盖文件中,我无法访问 Bootstrap 的 _variables.scss 中定义的变量,如果我想使用其他 Bootstrap 变量定义我的变量覆盖(或我自己的其他自定义变量),我将需要它。例如,我可能想这样做:$custom-var: $grid-gutter-width / 2;

已解决,但我不知道这适用于哪个版本。我相信 解决方案可能一直可用。测试于:

> sassc --version

sassc: 3.2.1
libsass: 3.2.5
sass2scss: 1.0.3

我们将使用简化的环境,因此文件名与 Bootstrap 的不匹配。


挑战

给定一个我们无法控制的框架(例如仅安装在持续集成环境中并且在我们的机器中不可用)以下列方式表达 SCSS 变量:

// bootstrap/_variables.scss

$brand-primary: #f00 !default;
$brand-warning: #f50 !default;

$link-color: $brand-primary !default;

并在同一框架中给出一个使用变量的文件:

// bootstrap/main.scss

a:link, a:visited {
  color: $link-color;
}

挑战是:

Include the framework in your own application’s SCSS in such a way that

  1. variables’ dependencies in the framework are preserved and honors;
  2. you can depend in on the default values but still be able to change the results on the framework dependencies.

更准确地说:

Include the framework in your application’s SCSS in such a way that $brand-color will always be the inverse of $brand-warning, whatever its value is in the framework.

解决方案

主文件如下所示:

// application.scss

@import "variables";
@import "bootstrap/variables";
@import "bootstrap/main";

您的变量文件将如下所示:

// _variables.scss

%scope {
  @import "bootstrap/variables";

  $brand-primary: invert($brand-warning) !global;
}

结果:

> sassc main.scss

a {
  color: blue; }

说明

%scope 部分并不是 SCSS 的魔法,它只是一个隐藏的 class,名称为 scope,专门用于以后的 @extend 扩展。我们只是用它来创建一个变量范围(因此得名)。

在范围内,我们 @import 框架的变量。因为此时每个变量都没有值,每个变量都被创建并分配了它的 !default 值。

但这就是噱头。变量不是全局,而是局部。我们可以访问它们,但它们不会污染全局范围,该范围稍后将用于在框架内派生变量。

事实上,当我们想要定义我们的变量时,我们希望它们是全局的,实际上我们使用 !global 关键字来指示 SCSS 将它们存储在全局范围内。


注意事项

有一个重要警告:您不能在定义变量时使用自己的变量

这意味着在这个文件中

%scope {
  @import "bootstrap/variables";

  $brand-primary: black !global;

  @debug $brand-primary;
}

@debug语句将打印默认值定义在bootstrap/_variables.scss,而不是black.

解决方案

将变量分成两部分:

%scope {
  @import "bootstrap/variables";

  $brand-primary: black !global;

  @debug $brand-primary;
}

@debug $brand-primary;

第二个@debug确实会正确打印black

使用 Bootstrap 4 或 bootstrap-sass 所有变量都设置在 _variables.scss 中,带有 !default 标志。

因此,如果您在包含 bootstrap 的 _variables.scss 之前设置了一个变量,当它被包含时,来自 _variables.scss 的值将被忽略。

所以我的 sass 条目文件可能看起来像这样...

@import "bootstrap-overrides";
@import "bootstrap/scss/bootstrap-flex";
@import "mixins/module";

在 Bootstrap 4 的 alpha 6 中,_variables.scss 中的所有变量都可以按照 mryarbles 描述的方式在 _custom.scss 中覆盖。 但是,覆盖不会级联到其他元素,因为包含顺序是:

@import "variables";
@import "mixins";
@import "custom";

当我将其更改为

@import "custom";
@import "variables";
@import "mixins";

它按预期工作。

BS4 dev 分支中的 _custom.scss 文件已被删除。尝试使用此订单重新订购您的进口商品:

设置

@import "client-variables";
@import "app-variables";
@import "boostrap";
@import "app-mixins";
@import "client-mixins";

确保将 boostrap 变量文件 _variables.scss 的内容复制到 app-variablesclient-variables。保留每个变量旁边的 !default 以允许进一步覆盖。

说明

所有 bootstrap 个变量都用 !default 声明。来自 Sass reference:

You can assign to variables if they aren’t already assigned by adding the !default flag to the end of the value. This means that if the variable has already been assigned to, it won’t be re-assigned, but if it doesn’t have a value yet, it will be given one.

  • Bootstrap 将尊重已在顶部定义的所有变量,使 app_variables 具有更高优先级,client_variables 具有最高优先级。
  • 您需要将 bootstrap _variables 中的所有变量声明复制到 app-variablesclient-variables 中,以便您可以根据需要拥有自定义变量。 (缺点是每次 bootstrap 更新都很难维护)
  • 所有变量现在都可以在 app-mixinsclient-mixins
  • 中使用