Forget CSS-In-JS: Combine Sass With CSS Modules Using Webpack

I really dislike the CSS-In-JS trend. Nothing against anyone who is a fan, but writing CSS inside of Javascript doesn’t feel natural and honestly, it’s just an unnecessary abstraction. I understand why it became a thing, but the problems CSS-In-JS promises to solve have already been solved thanks to CSS Modules and Shadow DOM.

As usual, the front-end development community are focused on tooling and not on the end-user experience. Numerous benchmarks have proven CSS-In-JS can introduce performance issues into your application.

For cross-platform development, I will not dispute CSS-In-JS solutions can be useful, but many of us only target one platform most of the time. The thought of having to write components for even the most simple of UI tasks sounds daunting.

I am a big fan of using CSS Modules in my projects. They solve the naming issue in CSS giving you the same result as something like Styled Components, only you get to write CSS inside of CSS files. In combination with a pre-processor, even better.

Separating module and non-module SCSS

When using CSS Modules in your application, it is considered best practice to separate your module and non-module CSS styles. If you treat all CSS/SCSS as a CSS Module, when working with third-party packages with CSS/SCSS such as Bootstrap, you will run into problems.

I like to treat all CSS/SCSS as non-module by default. For CSS Modules, you want to specifically opt-in to use the modules functionality. This way you can have traditional global CSS and when you want localised component styles, you opt-in.

For module styles I like to use the .module.scss or .module.css naming convention. Inside of my components I will do something like this:

import styles from './mycomponent.module.scss';

In my case, I am using autoprefixer as well as cssnano to add browser prefixes and de-duplicate my CSS styles when imported. Your webpack.config.js configuration might look slightly different to this, alter to your taste.

Define the loaders

const cssModuleRules = [{
        loader: 'css-loader',
        options: {
            importLoaders: 2,
            sourceMap: true,
            modules: {
                localIdentName: '[name]__[local]____[hash:base64:5]',
            },
        }
    },
    {
        loader: 'postcss-loader',
        options: {
            plugins: () => [
                require('autoprefixer')(),
                require('cssnano')()
            ]
        }
    }
];

const cssRules = [{
        loader: 'css-loader'
    },
    {
        loader: 'postcss-loader',
        options: {
            plugins: () => [
                require('autoprefixer')(),
                require('cssnano')()
            ]
        }
    }
];

We have two CSS Loader configurations here, one for CSS Modules and one for standard CSS.

The following loader configurations going inside of the module rules property.

{
    test: /\.css$/i,
    use: extractCss ? [{ loader: MiniCssExtractPlugin.loader }, ...cssRules] : ['style-loader', ...cssRules]
},
{
    test: /\.scss$/,
    exclude: /\.module\.scss$/,
    use: extractCss ? [{ loader: MiniCssExtractPlugin.loader }, ...cssRules, ...sassRules] : ['style-loader', ...cssRules, ...sassRules],
},
{
    test: /\.module\.scss$/,
    use: extractCss ? [{ loader: MiniCssExtractPlugin.loader }, ...cssModuleRules, ...sassRules] : ['style-loader', ...cssModuleRules, ...sassRules]
}

We have a CSS rule for being able to deal with standard CSS, not using modules. Then we have a scss rule for non-module scss styling. And lastly, we have a rule for module styles using our CSS Modules configuration.

If you wanted to use CSS Modules with standard CSS, you could easily add another rule testing for .module.css to allow that as well.

That is all there really is to it. Now, you have CSS Modules allowing locally scoped CSS that will have randomly generated class name strings, allowing you to write specific CSS without global naming conflicts.

Leave a Reply

Your email address will not be published. Required fields are marked *