ShakaCode | ShakaCode Blog | Rails On Maui Blog | Rails | ReactJs | JavaScript | Webpack | Productivity |

Best practices for CSS and CSS Modules using Webpack


#1

In the past, I’ve normally used the Rails asset helpers to load a global CSS file, and I’ve used CSS modules (and the sass-resources-loader for bits of CSS that are local to the React components within one directory.

CSS Modules

For CSS modules the syntax looks like this:

import styles from './style.css';
...
<button className=`${styles.primaryButton}`>Confirm</button>

In the webpack config, the key is to set the modules query option to true for the css-loader module, per the instructions: css-loader

The query parameter modules enables the CSS Modules spec.

This enables local scoped CSS by default. (You can switch it off with :global(...) or :global for selectors and rules.)

Another issue that applies to CSS module includes is that you don’t have access to the global variables, unless you use the sass-resources-loader

Here’s an interesting idea to wrap a library CSS file inside of a class. You can find more details here.

// Select.scss (SASS + CSS Modules)
.wrap {
    :global {
        @import "node_modules/react-select/scss/default.scss";
    }
}

With that CSS, you’d wrap the library component in a div with the css module wrap class:

const Select = (props) => {
    return (
        <div className={styles.wrap}><ReactSelect {...props} /></div>
    )
};

Global CSS

If you are using the CSS modules option in your webpack config and you want to include a CSS or SASS file without the CSS module changes to the class names, you have two options:

  1. You can configure your webpack config with a test that matches files you want to globally include, and have the test for files to use CSS modules include a look-ahead regexp to exclude the file. Here’s an example. Notice the fancy regexp in the second test.
module: {
    rules: [
      {
        test: /(\.global\.css$|react-select.css)/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
        ],
      },
      {
        test: /^((?!\.global|react-select).)*\.css$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 1,
              sourceMap: true,
            },
          },
    ],
  },
  1. You can create a wrapper file and import the CSS or SASS:
  :global {
      @import "file1.scss";
      @import "file2.scss";
  }

An advantage of the second option is that you have better control over the ordering of the sass files, which matters for the inclusion of variables, computations based on variables, and overriding selectors.


#2

We’re usually using css instead of styles as the variable name by convention.

If you want to import something from node modules, just use ~ instead of node_modules like that. See my quick tip.

I wouldn’t use the name wrap is it’s not semantically meaningful. Your component is named Select, so make your root node’s class name Select. Then you can do:

// Select.scss (SASS + CSS Modules)
.Select {
    :global {
        @import "~react-select/scss/default.scss";
    }
}

To be clear to readers, the reason for doing this is that we don’t want the class names to pollute the global namespace. So we import the styles so they will only take effect on things inside of the Select component. Then you can style different instances where you use the react-select CSS files differently.


#3

Yeah, I prefer :global way as it’s more explicit.

Also, you can simplify this:

use: [
  {
    loader: 'style-loader',
  },
  {
    loader: 'css-loader',
  },
],

// same:
use: [
  'style-loader',
  'css-loader',
],