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.