Bootstrap SASS变量覆盖挑战

编辑:这个问题被标记为重复这一个,但看到接近这个答案的末尾增编,看看这个问题不问,什么回答不回答。

我正在使用使用Bootstrap 3的Web应用程序。我有一个基本的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

理论上很简单,但是由于我将其称为“变量依赖项”而出现了问题-变量定义为其他变量。例如:

$brand: blue;
$text: $brand;

Now, let's say the above variables are defined in _variables.scss. Then let's say in _app-variables.scss, I override only the $brand variable to make it red: $brand: red. Since SASS interprets the code line by line sequentially, it will first set $brand to blue, then it will set $text to blue (because $brand is blue at that point), and finally it will set $brand to red. So the end result is that changing $brand afterwards does not affect any variables that were based on the old value of $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.

But obviously that's not what I want - I want my change of $brand to affect everything that depends on it. In order to properly override variables, I'm currently just making a full copy of _variables.scss into _app-variables.scss, and then making modifications within _app-variables from that point. And similarly I'm making a full copy of _app-variables.scss into _client-variables.scss and then making modifications within _client-variables.scss at that point. Obviously this is less than ideal (understatement) from a maintenance point of view - everytime I make a modification to _variables.scss (in the case of a Bootstrap upgrade) or _app-variables.scss, I have to manual trickle the changes down the file override stack. And plus I'm having to redeclare a ton of variables that I may not even be overriding.

I found out that LESS has what they call "lazy loading" (http://lesscss.org/features/#variables-feature-lazy-loading), where the last definition of a variable is used everywhere, even before the last definition. I believe this would solve my problem. But does anyone know a proper variable-override solution using SASS?

ADDENDUM:
Here's one technique I've already thought through: include the files in reverse order, using !default for all variables (this technique was also suggested in the answer to this question). So here's how this would play out:

_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)

So that solution is almost perfect. However, now in my override files, I don't have access to variables defined in Bootstrap's _variables.scss, which I would need if I wanted to define my variable overrides (or my own additional custom variables) using other Bootstrap variables. For example, I might want to do: $custom-var: $grid-gutter-width / 2;

番长樱梅2020/04/03 10:46:47

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

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

当我将其更改为

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

它按预期工作。

凯西里2020/04/03 10:46:47

_custom.scssBS4 dev分支中文件已被删除。尝试使用以下顺序对您的进口商品重新排序:

设定

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

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

说明

所有引导变量均以声明!default从Sass 参考

如果尚未分配变量,则可以通过在值的末尾添加!default标志来分配它们。这意味着,如果变量已被分配给它,则不会被重新分配,但是如果它还没有值,它将被赋予一个值。

  • Bootstrap will respect all variables already defined on top making the app_variables with higher priority and client_variables with highest priority.
  • You need to copy all the variable declarations from bootstrap _variables into app-variables and client-variables so you can have a custom variable as you wanted. (Disadvantage is that it is harder to maintain on every bootstrap update)
  • All variables are now available in your app-mixins and client-mixins
LGil2020/04/03 10:46:47

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

因此,如果在包含引导程序的_variables.scss之前设置了变量,则在包含该变量时,将忽略_variables.scss中的值。

所以我的Sass入口文件可能看起来像这样...

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

蛋蛋2020/04/03 10:46:47

Solved, but I don’t know from which version this works. I believe the solution could have always been available. Tested on:

> sassc --version

sassc: 3.2.1
libsass: 3.2.5
sass2scss: 1.0.3

We are going to use a simplified environment, so filenames do not match with Bootstrap’s.


Challenge

Given a framework we do not control (for example installed only on the Continuous Integration environment and not available in our machines) that expresses SCSS variables in the following manner:

// bootstrap/_variables.scss

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

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

And given a file in that same framework that uses the variables:

// bootstrap/main.scss

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

The challenge is:

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.

More precisely:

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.

Solution

The main file would look like this:

// application.scss

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

And your variables file would look like this:

// _variables.scss

%scope {
  @import "bootstrap/variables";

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

Results:

> sassc main.scss

a {
  color: blue; }

Explanation

The %scope part is not something magic of SCSS, it’s simply a hidden class with the name scope, available exclusively for later extensions with @extend. We are using it just to create a variable scope (hence the name).

Inside the scope we @import the framework’s variables. Because at this moment there’s no value for each variable every variable is created and assigned its !default value.

But here’s the gimmick. The variables are not global, but local. We can access them but they are not going to pollute the global scope, the one that will be later used to derive variables inside the framework.

In fact, when we want to define our variables, we want them global, and indeed we use the !global keyword to signal SCSS to store them in the global scope.


Caveats

There’s one major caveat: you cannot use your own variables while you define them.

That means that in this file

%scope {
  @import "bootstrap/variables";

  $brand-primary: black !global;

  @debug $brand-primary;
}

The @debug statement will print the default value defined in bootstrap/_variables.scss, not black.

Solution

Split variables in two parts:

%scope {
  @import "bootstrap/variables";

  $brand-primary: black !global;

  @debug $brand-primary;
}

@debug $brand-primary;

The second @debug will indeed correctly print black.