Currently migrating from sass to dart-sass.
Using #use and #forward is not a problem for any *.scss file outside shared folder.
Problem:
When to stylesheets/shared/_variables.scss add #use "functions" as *; as it uses function example-ui-color, getting:
Question:
Is there some different approach to achieve this?
Error: Module loop: this module is already being loaded.
┌──> app/assets/stylesheets/shared/_functions.scss
1 │ #use "variables" as *;
│ ^^^^^^^^^^^^^^^^^^^^^ new load
╵
┌──> app/assets/stylesheets/example/example.scss
1 │ #use "shared/variables" as *;
| │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ original load
| ╵
| shared/_functions.scss 1:1 #use
| shared/_variables.scss 3:1 #use
| app/assets/stylesheets/example/example.scss 1:1 #use
| app/assets/stylesheets/application.scss 2:1 root stylesheet
File structure:
stylesheets/shared/
_functions.scss
_mixins.scss
_variables.scss
stylesheets/example/
example.scss
stylesheets/application.scss
// stylesheets/shared/_functions.scss
#use "variables" as *;
#use "sass:map";
#function example-ui-color($color, $variant: 'normal') {
#return map.get(map.get($example-ui, $color), $variant);
}
// stylesheets/shared/_mixins.scss
#use "variables" as *;
#use "sass:list";
#use "sass:map";
#mixin break($size) {
#media (min-width: map.get($breakpoints, $size)) {
#content;
}
}
// stylesheets/shared/_variables.scss
#use "functions" as *; // when add this, error happens
$example-ui: (
'grey': ('lightest': #eeeeee, 'light': #d2d2d2, 'normal': #c1c1c1, 'dark': #999, 'darkest': #3a3a3a),
'green': ('lightest': #d7e8c4, 'light': #bde093, 'normal': #6cc04a, 'dark': #28a834, 'darkest': #00833e),
'blue': ('lightest': #cce5f4, 'light': #b6c2e9, 'normal': #62b3e5, 'dark': #0b3486, 'darkest': #071d49),
'red': ('lightest': #f7eae4, 'light': #f0b3ca, 'normal': #e45385, 'dark': #dd3a76, 'darkest': #f9423a),
'orange': ('lightest': #ffe1c6, 'light': #ffbe85, 'normal': #f78d2d, 'dark': #e57a18, 'darkest': #d75f00),
);
$breakpoints: (
xs: 360px,
sm: 768px,
md: 992px,
lg: 1024px
);
$footer_border_top: example-ui-color('blue', 'darkest');
$gap: 16px;
// stylesheets/example/example.scss
#use "shared/variables" as *;
#use "shared/mixins" as *;
#use "shared/functions" as *;
li.responsible-contact a {
border: 1px solid example-ui-color('grey', 'normal');
}
You are using advanced Sass concepts that I don't use myself but something is quite obvious here :
_functions.scss partial uses variables
_variables.scss partial uses functions
It is recursive and you end up in a loop.
Basically you have to shape your partials in a way they don't rely on each other.
for example I have a folder abstracts which contains the following partials :
_colors.scss
_mixins.scss
_responsive.scss
_values.scss
And none of them uses any other partial. Because each of these partials do their own job. For example Colors have only color variables ... They are basically atomic.
Maybe you can rethink the way you design your SCSS framewok to have it work without any recursion
One idea is to create a higher level partial that would uses many partials from your shared folder and use this partial instead of the children partials independently.
Related
Does anybody know how I can use values that I have passed to a sass module (like shown below) inside another sass module that is not in the same scope but uses the module with #use?
I have a project where Sass modules are used in different files that don't always have the same styles.scss as a base. In fact I include our "core" via npm as a node module.
The core has a _core.scss file, and for every core-component a folder with at least 2 files. aA _config.scss file and a _functions.scss file and sometimes a _mixins.scss and a _generate.scss.
here is an example of the _core.scss:
#forward "colors/config";
#forward "colors/functions";
and the colors _config.scss file looks like this:
$colors: (
"primary": #8fc5de,
"secondary": #f25116,
"accent": #bcd955,
) !default;
and the _functions.scss like this:
#use "sass:map";
#use "config" as colors-cfg;
#function color($color-name) {
#if map.has-key(colors-cfg.$colors, $color-name) {
#return var(--adm-color-#{$color-name});
} #else {
#return var(--adm-color-unknown);
}
}
In the project we have 2 specific areas:
the sass folder, where we create all global stylings for the project itself
the components folder, where we style some components who can be used as webcomponents with a shadow dom (optional)
Example of styles.scss in the sass folder
#use "config" as adm-cfg;
#use "nodemodulespath/core/core" as adm;
and the config file:
#use "nodemodulespath/core/colors/config" as colors-cfg with (
$colors: (
"primary": #f59e4b,
"secondary": #f25116,
"accent": #bcd955,
"othercolor": #383723,
)
);
So far so good.
Now I want to use the colors with my color function inside the header.style.scss file. And here I have some problems with the values of the config.
Example:
#use "nodemodulespath/core/core" as adm;
.h1 {
color: adm.color("othercolor");
}
I can't use "othercolor" which i have added to the $colors map in _config.scss inside my color() function. I can only access the colors that are initially added inside the colors config from the core.
Inside the sass Folder i can access everything fine.
Any ideas how i can fix this? I already tried to pass new config values inside of header.style.scss like so:
#use "nodemodulespath/core/colors/config" as colors-cfg with (
$colors: (
"primary": #f59e4b,
"secondary": #f25116,
"accent": #bcd955,
"othercolor": #383723,
)
);
#use "nodemodulespath/core/core" as adm;
.h1 {
color: adm.color("othercolor");
}
But then I get the sass error that I can use the with() option just once.
Would be super cool if someone with a deeper understanding of the sass module system can help me with this one.
thank you!
I am using a package called Buefy which is a Vue.js wrapper for the Bulma CSS framework library. Buefy puts an attribute/property on its template components called type (e.g., type="is-warning"). As per the Buefy documentation, the term "is-warning" is pre-defined according to the $colors variable set within SCSS:
So I followed these instructions from Buefy to be able to customize the the $colors map, but what I would like to do, however, is keep the initial SCSS defined in those instructions within a single base.scss file, and then extend with my own customized SCSS file.
I have included the base.scss file in my Vue project with the following snippet in the vue.config.js file:
module.exports = {
css: {
loaderOptions: {
sass: {
prependData: `
#import "#/assets/scss/base.scss";
`
}
}
}
}
So my question is, how can I extend the $colors map in the base.scss with my own names and values?
This is a snippet from base.scss, where the $colors map is defined:
```css
$colors: (
"white": ($white, $black),
"black": ($black, $white),
"light": ($light, $light-invert),
"dark": ($dark, $dark-invert),
"primary": ($primary, $primary-invert),
"info": ($info, $info-invert),
"success": ($success, $success-invert),
"warning": ($warning, $warning-invert),
"danger": ($danger, $danger-invert),
"twitter": ($twitter, $twitter-invert)
);
```
So, again, my question is how can I extend the $colors map in a different file outside of base.scss (perhaps in App.vue) to leave the original untouched? I didn't see any solution shown in the scss docs.
Your custom color values would go in a variable (later merged into $colors) called $custom-colors, and it's actually described in the docs on "derived variables".
And to customize it:
// Import Bulma's core
#import "~bulma/sass/utilities/_all";
// Set your colors
$custom-colors: (
"facebook": ($blue, $white),
"linkedin": ($dark-blue, $white)
// etc.
);
// Setup $colors to use as bulma classes (e.g. 'is-twitter')
$colors: (
"white": ($white, $black),
"black": ($black, $white),
"light": ($light, $light-invert),
"dark": ($dark, $dark-invert),
"primary": ($primary, $primary-invert),
"info": ($info, $info-invert),
"success": ($success, $success-invert),
"warning": ($warning, $warning-invert),
"danger": ($danger, $danger-invert),
"twitter": ($twitter, $twitter-invert)
);
// Import Bulma and Buefy styles
#import "~bulma";
#import "~buefy/src/scss/buefy";
It's internally using a custom function called mergeColorMaps.
I have a partial sass called "_color.scss" where it has these code from materializecss.
$materialize-red: (
"base": #e51c23,
"lighten-5": #fdeaeb,
"lighten-4": #f8c1c3,
"lighten-3": #f3989b,
"lighten-2": #ee6e73,
"lighten-1": #ea454b,
"darken-1": #d0181e,
"darken-2": #b9151b,
"darken-3": #a21318,
"darken-4": #8b1014,
);
...
#each $color_name, $color in $colors {
#each $color_type, $color_value in $color {
#if $color_type == "base" {
.#{$color_name} {
background-color: $color_value !important;
}
.#{$color_name}-text {
color: $color_value !important;
}
}
#else if $color_name != "shades" {
.#{$color_name}.#{$color_type} {
background-color: $color_value !important;
}
.#{$color_name}-text.text-#{$color_type} {
color: $color_value !important;
}
}
}
}
// Shade classes
#each $color, $color_value in $shades {
.#{$color} {
background-color: $color_value !important;
}
.#{$color}-text {
color: $color_value !important;
}
}
// usage: color("name_of_color", "type_of_color")
// to avoid to repeating map-get($colors, ...)
#function color($color, $type) {
#if map-has-key($colors, $color) {
$curr_color: map-get($colors, $color);
#if map-has-key($curr_color, $type) {
#return map-get($curr_color, $type);
}
}
#warn "Unknown `#{name}` in $colors.";
#return null;
}
Then in my every other .scss files, i import that in.
#import "../../bower_components/materialize/sass/components/color";
Given this statement by their site, "The underscore lets Sass know that the file is only a partial file and that it should not be generated into a CSS file.", should this be excluded from the actual css? But my current environment is including the partial into my css
was it something to do with my grunt?
You shouldn't have to include the partial file in every over sass file, it defeats the purpose.
What does your file structure look like?
For example, my file structure would look something like:
main.scss
_variables.scss
_mixins.scss
_grid.scss
_whatever.scss
and then within main.scss
#import 'variables'
#import 'mixins'
#import 'grid'
#import 'whatever'
With grunt or sass you are then compiling main.scss , or using sass:
sass --watch main.scss:style.css
Without the underscore, there is a chance that your build tool (grunt) will output a CSS file for every SCSS file in your folders. For example, with this folder structure (without underscores):
my_project
scss
main.scss
colors.scss
mixins.scss
component1.scss
component2.scss
grunt might output a CSS file for every partial:
my_project
css
main.css
colors.css
mixins.css
component1.css
component2.css
Most people don't want this, so we use underscores to tell grunt not to compile every file, like so:
my_project
scss
main.scss
_colors.scss
_mixins.scss
_component1.scss
_component2.scss
This will output as:
my_project
css
main.css
Looking at the Angular Material documentation, they recommend using a -theme file per component to manage applying any theme-related styles to a specific class.
From my perspective, some downsides to this approach are:
quite verbose
splits up the style into two different locations
all your frontend devs need to understand how Angular Material colours work
makes it harder to change values (e.g. we might've been using mat-color($primary, 200) for border colours and now want to change it to mat-color($primary, 300). This will have been repeated all throughout the codebase.
Given a consistent design language, there will only be a subset of colors that will be used (e.g. 4 colors from the primary palette, 3 from the accent palette, a few different foreground / background colors, etc).
Given the above, doesn't it make more sense to have a _colors.scss that defines the exact colors using the theme rather than hoping developers extract the correct value from the theme each time?
e.g. maybe something like:
$clr-primary-default: mat-color($primary);
$clr-primary-contrast: mat-color($primary, default-contrast);
$clr-primary-light: mat-color($primary, lighter);
$clr-primary-dark: mat-color($primary, darker);
$clr-accent-default: mat-color($accent);
$clr-accent-light: mat-color($accent, lighter);
$clr-accent-dark: mat-color($accent, darker);
$clr-default-text: mat-color($foreground);
$clr-secondary-text: mat-color($foreground, secondary-text);
//etc
Then rather than creating a separate -theme file for each component that requires specific colors, I can simply import the colors.scss file and use the variables directly in the *.component.scss file.
Just wanting to validate that the above is sound and that I'm not missing anything obvious that's going to cause pain down the track?
The other tricky part is how to actually define these in a separate colors file efficiently given the file will need access to the theme data.
Why use #mixin?
Just wanting to validate that the above is sound and that I'm not missing anything obvious that's going to cause pain down the track?
The only thing, I can think of, is that you would miss the opportunity to use multiple themes in one application. With the approach from the Angular Material documentation, you would have a #mixin for each component, that you can #include multiple times with different $theme variables.
Example from https://medium.com/#tomastrajan/the-complete-guide-to-angular-material-themes-4d165a9d24d1:
.default-theme {
#include angular-material-theme($theme);
#include custom-component-theme($theme);
}
.light-theme {
#include angular-material-theme($light-theme);
#include custom-component-theme($light-theme);
}
This wouldn't work, if you import colors as scss-variables into your components and use it there.
Separate file for colors
The other tricky part is how to actually define these in a separate colors file efficiently given the file will need access to the theme data.
This is actually pretty straight forward: I have a separate file src/styles/_variables.scss that contains my custom colors as scss-variables and also the $theme variable, that I am using later in src/theme.scss.
#import '~#angular/material/theming';
// Theme configuration
$primary: mat-palette($mat-blue, 800, 500, 900);
$accent: mat-palette($mat-blue, A200, A100, A400);
$warn: mat-palette($mat-red);
$theme: mat-light-theme($primary, $accent, $warn);
// Custom colors
$custom-colors: (
custom-color-a: mat-color($mat-green, 700),
custom-color-b: mat-color($mat-red, 400),
);
$theme: map-merge($theme, (custom-colors: $custom-colors));
To import my _variables.scss inside a component, I have to add stylePreprocessorOptions to the angular.json file:
"styles": [
"src/styles.scss",
"src/theme.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"src/styles"
]
},
Now I can import my variables in all scss-files of my components:
#import 'variables';
.custom-class-a {
background-color: map-get($custom-colors, custom-color-a);
color: map-get($custom-colors, custom-color-b);
}
Why do I use a sass-map and map-merge?
As you noticed, I collect my custom colors in the sass-map $custom-colors and merge them into my $theme variable. This way I could either use my custom colors by directly importing it into my components style sheet (as described above) or I could use them inside my components #mixin the way it is described in the Angular Material documentation.
#import '~#angular/material/theming';
#mixin custom-component-theme($theme) {
$custom-colors: map-get($theme, custom-colors);
.custom-class-a {
background-color: map-get($custom-colors, custom-color-a);
color: map-get($custom-colors, custom-color-b);
}
}
Maybe this combination is a way that your frontend devs could work with?
I have defined primary,accent and warn colors as css custom variables in the styles.css file like so:
#import "~#angular/material/theming";
#include mat-core();
$my-primary: mat-palette($mat-blue-grey);
$my-accent: mat-palette($mat-amber, A200, A100, A400);
$my-warn: mat-palette($mat-deep-orange);
$my-2-primary: mat-palette($mat-pink, 400, 200, 600);
$my-2-accent: mat-palette($mat-blue, A200, A100, A400);
$my-2-warn: mat-palette($mat-deep-orange, 500, 300, 700);
.dark-theme {
$my-theme-dark: mat-dark-theme($my-primary, $my-accent, $my-warn);
#include angular-material-theme($my-theme-dark);
$primary: mat-color($my-primary);
$accent: mat-color($my-accent);
$warn: mat-color($my-warn);
$fg_palette:map-get($my-theme-dark, foreground);
$bg_palette:map-get($my-theme-dark, background);
$fg:map-get($fg_palette, text);
$bg:map-get($bg_palette, background);
--primary: #{$primary};
--accent: #{$accent};
--warn: #{$warn};
--fg: #{$fg};
--bg: #{$bg};
}
.dark-theme-2 {
$my-2-theme-dark: mat-dark-theme($my-2-primary, $my-2-accent, $my-2-warn);
#include angular-material-theme($my-2-theme-dark);
$primary: mat-color($my-2-primary);
$accent: mat-color($my-2-accent);
$warn: mat-color($my-2-warn);
$fg_palette:map-get($my-2-theme-dark, foreground);
$bg_palette:map-get($my-2-theme-dark, background);
$fg:map-get($fg_palette, text);
$bg:map-get($bg_palette, background);
--primary: #{$primary};
--accent: #{$accent};
--warn: #{$warn};
--fg: #{$fg};
--bg: #{$bg};
}
And used these variables in any of my components like so:( in my-custom-component.scss)
.some-class {
color: var(--primary)
}
.another-class {
background-color: var(--bg)
}
.yet-another-class {
border-color: var(--accent)
}
By doing like this, i can change any value related to color in any component, because these variables are global (defined in styles.css)
As i change theme, these colors also change according to new theme's color
I am working on a project where I used the Material 2 Themes and I used this approach where I use the class name and add colors class globally.
This is what I did :
FileName: mytheme-sidemenu.scss:
// Import all the tools needed to customize the theme and extract parts of it
#import "~#angular/material/theming";
// Define a mixin that accepts a theme and outputs the color styles for the component.
#mixin mytheme-sidemenu($theme) {
// Extract whichever individual palettes you need from the theme.
$primary: map-get($theme, primary);
$accent: map-get(
$theme,
accent
); // Use mat-color to extract individual colors from a palette as necessary.
.col-primary {
color: mat-color($primary, 500) !important;
}
.col-accent {
color: mat-color($accent, 300) !important;
}
}
Here is my main theme file:
mytheme-theme.scss:
#import '~#angular/material/theming';
#import './variables/helper.scss';
#import './variables/spacemanager.scss';
#import './mytheme-sidemenu.scss';
// Primary theme
#include mat-core();
$mytheme-app-primary: mat-palette($mat-light-blue, 700, 600);
$mytheme-app-accent: mat-palette($mat-pink, A200, 900, A100);
$mytheme-app-warn: mat-palette($mat-deep-orange);
$mytheme-app-theme: mat-light-theme($mytheme-app-primary, $mytheme-app-accent, $mytheme-app-warn);
#include angular-material-theme($mytheme-app-theme);
// Secondary Theme
.mytheme-alt-theme {
$mytheme-alt-primary: mat-palette($mat-blue-grey, 500);
$mytheme-alt-accent: mat-palette($mat-pink, 500);
$mytheme-alt-warn: mat-palette($mat-deep-orange);
$mytheme-alt-theme: mat-light-theme($mytheme-alt-primary, $mytheme-alt-accent, $mytheme-alt-warn);
#include angular-material-theme($mytheme-alt-theme);
}
// Using the $theme variable from the pre-built theme you can call the theming function
#include mytheme-sidemenu($mytheme-app-theme);
and in app.module.ts update this :
export class AppModule {
constructor(
#Inject(OverlayContainer) private overlayContainer: OverlayContainer
) {
this.overlayContainer
.getContainerElement()
.classList.add("mytheme-alt-theme"); // this for double theme add to the root css class
}
}
I have Font Awesome v4.7.0 installed and I'm trying to write Sass classes that extend Font Awesome icon classes, like this:
div.edit-icon {
#extend .fa-pencil-square-o;
font-size: $icon-font-size;
}
At the start of one of my SCSS files (_shared.scss), I try importing the bare essentials I need from Font Awesome (installed in node_modules):
#import '~font-awesome/scss/variables';
#import '~font-awesome/scss/mixins';
#import '~font-awesome/scss/icons';
However, Webpack gives me this error when I save the file:
ERROR in ./~/sass-extract-loader!./app/views/components/_shared.scss
Module build failed: Error: File to import not found or unreadable: ~font-awesome/scss/variables.
Parent style sheet: C:/Users/<me>/WebstormProjects/<project>/app/views/components/_shared.scss
at options.error (C:\Users\<me>\WebstormProjects\<project>\node_modules\node-sass\lib\index.js:291:26)
# ./app/views/components/candb/MessageDefinitionView/CoreMessageDefinitionView.tsx 9:28-77
# ./app/views/components/candb/MessageDefinitionView/TxMessageDefinitionView.tsx
# ./app/views/components/candb/index.ts
# ./app/views/components/index.ts
# ./app/views/layouts/Page/Page.tsx
# ./app/routes.tsx
# ./app/index.tsx
# multi (webpack)-dev-server/client?http://localhost:1212 webpack/hot/dev-server react-hot-loader/patch webpack-dev-server/client?http://localhost:1212/ webpack/hot/only-dev-server ./app/index.tsx
Here's the start of node_modules\font-awesome\scss\_variables.scss:
// Variables
// --------------------------
$fa-font-path: "../fonts" !default;
$fa-font-size-base: 14px !default;
$fa-line-height-base: 1 !default;
//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly
$fa-css-prefix: fa !default;
$fa-version: "4.7.0" !default;
$fa-border-color: #eee !default;
$fa-inverse: #fff !default;
$fa-li-width: (30em / 14) !default;
What I noticed is that the value of $fa-css-prefix is an unquoted string (fa), and that commenting out this assignment allows this SCSS file to compile.
How is it legal for this string to be unquoted, and what can I do to allow me to import this SCSS file?
For reference, this is a relevant part of my Webpack config for loading SCSS modules:
// Add SASS support - compile all other .scss files and pipe it to style.css
{
test: /^((?!\.global).)*\.scss$/,
loader: extractModuleCSS.extract({
fallback: 'style-loader',
use: [
'css-loader?modules&sourceMap&camelCase&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
'sass-loader'
]
})
}
Edit
I thought that the fact that Webpack displayed an error about a different file when I commented out $fa-css-prefix: ... meant that at least _variables.scss was compiling fine:
Undefined variable: "$fa-css-prefix".
in C:\Users\<me>\WebstormProjects\<project>\node_modules\font-awesome\scss\_icons.scss (line 4, column 4)
Apparently that isn't the case, though. If I comment out the other imports in _shared like this:
#import '~font-awesome/scss/variables';
//#import '~font-awesome/scss/mixins';
//#import '~font-awesome/scss/icons';
Then I still see the original error ('File to import not found or unreadable: ~font-awesome/scss/variables.') regardless of the changes I make to _variables.scss - even commenting out the entire file.
I've updated the title to reflect this new information.
Edit 2
Based on #CloudTseng's advice I tried this:
$fa-css-prefix: 'fa';
#import '~font-awesome/scss/variables';
#import '~font-awesome/scss/core';
#import '~font-awesome/scss/icons';
$icon-font-size: 16px;
div.edit-icon {
#extend .fa;
#extend .fa-pencil-square-o;
font-size: $icon-font-size;
}
div.cross-icon {
#extend .fa;
#extend .fa-times;
font-size: $icon-font-size;
}
Surprisingly, this gives me exactly what I want. The reason I find this surprising is that apparently I only needed to redefine fa-css-prefix locally - I was expecting that I would have to redefine all the Font Awesome variables from _variables.scss if I went this route.
However, inspecting my generated CSS shows me that all the other variables are magically resolved without me needing to redefine them:
font-awesome/scss/_icons.scss:
.#{$fa-css-prefix}-glass:before { content: $fa-var-glass; }
.#{$fa-css-prefix}-music:before { content: $fa-var-music; }
.#{$fa-css-prefix}-search:before { content: $fa-var-search; }
...
/dist/modules.css:
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.app-views-components-candb-FieldDefinitionTable-___FieldDefinitionTable__fa-glass___2rm6a:before {
content: "\F000"; }
.app-views-components-candb-FieldDefinitionTable-___FieldDefinitionTable__fa-music___3q-Vg:before {
content: "\F001"; }
.app-views-components-candb-FieldDefinitionTable-___FieldDefinitionTable__fa-search___f89bE:before {
content: "\F002"; }
...