2

I'm using Laravel Mix and Webpack for SASS pre-processing.

I have two "themes" in my website which I want to be lean, inheriting variables where they need to. For example, my primary theme will include in this order:

// Primary theme
@import "./primary-variables.scss";
@import "/path/to/default/theme/main.scss";

My default theme would look like this:

// Default theme
@import "./default-variables.scss";
@import "~bootstrap-sass/assets/stylesheets/_bootstrap";

Similarly to this question, I've included the primary variables first, then the default theme variables, then bootstrap last.

In my default theme I add !default to all variables so where they are redefining Bootstrap they will be used in priority, and where new they will be a default value. The primary theme doesn't use !default at all.

Working example

If Bootstrap defines $brand-danger as say red !default, my default theme redefines it as blue !default and my primary theme redefines it as yellow, my rendered output will be yellow - great!

The problem

When I need to reference variables that are only defined at other levels from my primary theme. For example:

// Primary theme:
// This fails since I haven't defined $brand-primary in my primary theme
$my-primary-theme-variable: $brand-primary;

The build now fails with an error saying primary-theme/src/scss/main.scss doesn't export content.

Workaround

I can work around this problem by copying the entire Bootstrap variables file through to my primary theme and changing variables as necessary, but I don't really want to do this.

Question

How does the SASS variable processor actually work? Is it possible for me to just change one of the Bootstrap variables in my theme without necessarily having to redefine the entire file?


This question is pretty similar.

Community
  • 1
  • 1
scrowler
  • 24,273
  • 9
  • 60
  • 92

2 Answers2

3

It seems like you are using @include to import your SCSS try using @import instead – If this is just a typo in the question please let me know :-)

@import "./primary-variables.scss",
        "/path/to/default/theme/main.scss"
;

I've added a few quick notes on the question you were referring to. The important thing to know about the !default flag is that it takes effect at the point when it is used in a selector and does not re-define variables.

Sass does not look ahead when processing variables – it prints out the current value. In this example .class-1 will be red as the re-definition comes after it being used in the selector and .class-2 will be blue as there is no default flag.

$brand-color: red !default; // defined
.class-1 { background-color: $brand-color; }  // red

$brand-color: blue;         // re-defined
.class-2 { background-color: $brand-color; }  // blue

Default flags will cause Sass to skip variable re-definition. In this example the result will be red as being defined first. The two following re-definitions are ignored because of the default flags.

$brand-color: red !default;   // defined
$brand-color: blue !default;  // ignored 
$brand-color: green !default; // ignored
.class-1 { background-color: $brand-color; }  // red

In this case all variables from from the config will be used – then variables from partial-1 if not defined in config and last partial-2 will define any variable not defined in the two others.

@import '_config.scss';    // definition
@import '_partial-1.scss'; // contains defaults
@import '_partial-2.scss'; // contains defaults

Hope it makes sense :-)


Import structure

//  _default-theme.scss
@import '_default-variables.scss', '_bootstrap.scss';

//  _primary-theme.scss
//  primary variables will override defaults or use defaults if not defined
@import '_primary-variables.scss', '_default-theme.scss';

// style.scss
@import '_primary-theme.scss'; // or '_default-theme.scss'

Scope

In case your default and primary has content that is unique to each theme you could create a scoping mixin to handle what is compiled.

Here is a very rudimentary version:

//  _scope.scss
$scope-context: null !default;
@function scope($scopes: null, $true: true, $false: false) { 
  @each $scope in $scope-context {  
    @if index($scopes, $scope) { @return $true }  
  }  
  @return $false;  
} 
@mixin scope($scopes: null) { 
  @if scope($scopes) or length($scopes) == 0 and not $scope-context { 
    @content; 
  } 
}

How it works

The scope mixin takes a context argument and a content block @content. If the passed context matches a global variable ($scope-context) the content block get's rendered.

//  _default-theme.scss
.class { content: 'Will show in both themes'; }

@include scope(default-theme){
  .class { content: 'Will only show in the default theme'; }
}

@include scope(primary-theme){
  .class { content: 'Will only show in the primary theme'; }
}

//  can also be used as "if" function
.class {
  content: scope(default-theme, 'Is default', 'Not default')
}

In your case define the $scope-context in both default and primary variables

// _default-variables.scss
$scope-context: default-theme !default;

// _primary-variables.scss
$scope-context: primary-theme;

... and add _scope.scss to the _default-theme.scss

//  _default-theme.scss
@import '_default-variables.scss', '_bootstrap.scss', '_scope.scss';   
Community
  • 1
  • 1
Jakob E
  • 4,476
  • 1
  • 18
  • 21
  • Sorry, yes- it's just a typo :-) – scrowler Feb 21 '17 at 17:17
  • Thanks - it makes sense and is easy to follow. My question is more about using variables that aren't defined at the time, but later on. For example - in your example, I might only want to define `$brand-color` in `_partial-2.scss`, but I might want to use it in `_config.scss` in another definition, e.g `$my-var: $brand-color`. This example is specific to bootstrap, where I might want to re-use a bootstrap variable in a custom definition, without having to redefine the entire bootstrap variables file so the variables are in the same scope. Is this possible? Or am I thinking wishfully? – scrowler Feb 22 '17 at 04:37
  • Two questions to clarify: Are your themes compiled into a single or two CSS files? Is the default theme only using default variables? – Jakob E Feb 22 '17 at 09:14
  • Yes- one compiled CSS file. It can be compiled from either of the themes depending on which you're using (the second theme inherits parts of the first, but the first is standalone). The default theme has some tweaked variables, as does the second theme. – scrowler Feb 22 '17 at 09:44
  • I've updated the answer (below the horizontal line) – hope we're getting closer :-) – Jakob E Feb 22 '17 at 13:14
  • Thanks for your help! That edit is actually less like what I want to do unfortunately, because for example I want the actual source sass files to be simple style sheets. I have had some luck playing with this yesterday in separating all variables out (including from within bootstrap) from actual stylesheets, importing all variables first then all styles. It means recreating the main bootstrap sass file (with all the imports) but it's working. – scrowler Feb 22 '17 at 18:16
2

The problem I found was that I was assuming things incorrectly about how SASS works.

When you define a variable declaration, the value of it is compiled at the time your write it. For example $my-var: $brand-primary would assign the current value of $brand-primary to $my-var at the time it is processed.

This means simply that I can't achieve what I wanted, which was to include a minimal variables file over the top of Bootstrap, because it would only update the variable itself, but not any other variables that reference that variable within Bootstrap.

The solution

It's not elegant, but duplicate the entire variable file for each theme and adjust them as required in each place.

scrowler
  • 24,273
  • 9
  • 60
  • 92
  • 2
    You should also check the highest ranked answer in question 31329177 (including my comment). The solution there is better IMO (but still not perfect). I think this is a flaw in SASS compared LESS, which would behave as wished in your question. Although this is "common" behavious in all programming languages I know it seems wrong to me in CSS-context. – bayerphi Aug 17 '17 at 16:30