4

goodday all,

I am trying to figure out if there is a way to link .scss in a way that they share their classnames.

So here is what I am trying to achieve.

|- theme
|   |- theme_default.scss
|- snippets
|   |-componentA
|   |   |-componentA.js
|   |   |-componentA.scss

In the ComponentA.js file I am attaching a class to the component from ComponentA.scss using the css-loader/CommonJS (I am using a webpack dev-environment if anyone needs to know).

In the theme_default.scss I am trying to apply a theme-style to 'ComponentA'. The default theme-file should be exchangable by other theme-files.

The problem is that the class-names (eventhough identical in the source-files) are hashed when compiled into regular '.css' files, meaning the class-name from the theme cssfile will never match the class-name from the ComponentA cssfile.

So how can I make clear the theme-file should use the same class-names as the Component-file without declaring the styles as global? I tried using @import and @use in the theme-file, but it only copies the rules while the class-names are still not identical.

Any input is welcome, thanks in advance for reading.

EDIT


On request some examples

theme_default.scss

.theme_default {
   .comp_a /* This class should be the same (hashed) as in the componentA.scss */ {
      background-color: red; /* awefull, but just for example sake */
   }
}

componentA.scss

.comp_a {
   display: flex;
   flex-wrap: no-wrap;
   /*.. etc.*/
}

componentA.js

import style from './componentA.scss'

// this is how I set the style, but this class does NOT know about any theme stylesheet. So nothing can be done from here
element.classList.add(style.comp_a);

To apply the theme I set the theme_default class (the hashed version) to a root element and let the browser figure out how to efficiently (re)render it.

I read that the css-loader has a mechanism with compose(s), which might solve this problem. But it seemingly doesn't work with either webpack or vsCode. So in any case someone needs it, here is the snippet from the webpack config file regarding css handling.

webpack.configuration.js

{
    test: /\.s[ac]ss$/i,
    include: [
        path.resolve(__dirname, 'src')
    ],
    exclude: [
            path.resolve(__dirname, 'node_modules'),
            path.resolve(__dirname, 'build'),
            path.resolve(__dirname, 'test'),
    ],
    use: [
        // Creates `style` nodes from JS strings
        isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
        // Translates CSS into CommonJS
        {
            loader: 'css-loader',
            options:
            {
                modules:
                {
                    exportGlobals: true,
                },
                importLoaders: 2,
                sourceMap: isDevelopment,
            }
        },
        // Transform urls
        {
            loader: 'resolve-url-loader',
            options:
            {
                sourceMap: isDevelopment
            }
        },
        // Compiles Sass to CSS
        {
            
            loader: 'sass-loader',
            options:
            {
                sassOptions:
                {
                    indentWidth: 4,
                    sourceMap: isDevelopment,
                    sourceMapContents: false,
                    outputStyle: 'expanded'
                },
            },
        }
    ],
},
n247s
  • 1,898
  • 1
  • 12
  • 30
  • 1
    Can you show an example of the rules in `theme_default.scss` and how you're using the classnames in `ComponentA.js`? I feel this may be a problem more related to CSS Modules/bundler than Sass – Coogie Aug 09 '20 at 13:31
  • @Coogan is this enough to go on, or do you need more/other information? – n247s Aug 10 '20 at 20:45
  • I think so. Hopefully I can help you out with this one – Coogie Aug 13 '20 at 20:58

1 Answers1

1

Theming is a little bit trickier with the use of CSS Modules, since the intent of it is to create locally-scoped CSS that isn't affected by the CSS cascade.

That being said, there are some options available to you depending on the route you'd like to take.


Argument/Prop Theming

Because CSS Modules don't want to be affected by the cascade, it's quite difficult to change component-level CSS by making use of class names higher in the DOM tree. The CSS-Modules project does have a page on theming, but I think it's a bit of a departure from how you're attempting to implement your styles. I would encourage you to consider it, though.

Their approach is to create a themed stylesheet per component and then import them into said component, switching out which one you're going to use based on a variable. In React, this would be a prop.

dark.css

.button {
  border: 1px solid #333;
}

neon.css

.button {
  border: 1px solid #0f0;
}

Button.js

export default class Button {
  constructor(theme) {
    this.theme = theme;
  }
  render() {
    const theme = this.theme;
    return `<div class="${theme.button}">Hello World</div>`;
  }
}

app.js

import darkTheme from "./dark.css";
import neonTheme from "./neon.css";

import Button from "./button";

// use component
new Button(dark);
new Button(neon);

This approach leans more into the traditional CSS Modules approach in which you'll duplicate some styles in your source, but you make up for it in reducing what you deliver to the client.

Usage with Sass

Since you're using Sass, you can cut down on the duplication and tediousness of changing things in the future by making use of Sass Mixins.

This is particularly helpful if you have a set of CSS properties that are common throughout many components that will consume them (background-color, border-color, etc), and will allow you to centralise these rules into a single file where they can be changed and propagate throughout other files.

themes.scss

@mixin neon {
  background-color: #0f0;
  border: 2px solid #ff0;
  color: #000;
}

button/neon.scss

.button {
  @include neon;
}

// Or, if you're using the latest Dart Sass and its @use rule
@use "themes";

.button {
  @include themes.neon;
}

This still requires you to set up and the theme as an application variable and pass it in as an arg or prop, but should reduce the need for you to duplicate the CSS rules in every file.


CSS Custom Properties

An alternative path available to you, if your browser support allows it, is the use of CSS Custom Properties. This would allow you to control values inside your CSS Module generated classnames from outside.

style.css

.button {
  background-color: var(--button-bg, #900); /* #900 default fallback if --button-bg doesn't exist*/
}

app.css

body {
  --button-bg: #f48024;
}

body.dark {
  --button-bg: #a34d08;
}
Coogie
  • 575
  • 6
  • 16
  • Thanks for your answer. I found the first option on te website of SASS itself as well. Unfortunatly it makes the system quit rigid (changing the theme afterwards is going to be tedious and I imagine much slower than a single css-class change). But if there is no good alternative I have to consider implementing it. Your second suggestion might be a valid approuch (especially with the fallback color). I'll see what I can come up with, thanks for your time anyways – n247s Aug 14 '20 at 08:12
  • Understandable. I agree that the CSS Modules system is somewhat restrictive in its implementation, but such things are necessary when trying to avoid the cascade. That being said, I _have_ updated the answer with an approach available to you in Sass that can help alleviate the problems/concerns you'll face with repetition in CSS Modules – Coogie Aug 14 '20 at 12:05
  • That might be a nice solution especially since you can 'program' this dynamically. I'll let evey option run by, and see what fits best. – n247s Aug 14 '20 at 13:38
  • Were you able to use any of the approaches as a solution, @n247s – Coogie Aug 19 '20 at 10:28
  • I haven't been able to continue on theming yet. Other portions of the project hold priority currently. I'll try to remember to report back once I have implemented a solution. – n247s Aug 19 '20 at 16:18