blog.jakoblind.no

How to configure CSS and CSS modules in webpack

One of the first thing you need to configure in your webpack project is CSS. CSS is so fundamental to a web app —​ almost all web apps needs it. But configuring webpack is tricky. And if you want CSS modules and CSS it can be even more confusing. In this article, I sort it all out for you.

You’ll learn the following in this post

  • How to configure basic CSS in a webpack project using style-loader and css-loader
  • How to extract the CSS to its own style.css file
  • How to configure CSS modules with webpack
  • How to configure both CSS and CSS modules

How to configure CSS with webpack

Let’s start configuring CSS. I assume you already have a webpack project set up. If you don’t, check out createapp.dev to create your own custom webpack boilerplate.

Before we configure CSS support in the webpack setup, let’s first add a CSS file and use it.

The first thing we are going to do is to add a styles.css file in the project. Let’s put it in the src folder.

body {
  color: white;
  background-color: black;
}

This CSS file creates a black background and white text color. This makes it clear to see if it works or not - if it works the whole page will be black!

Next thing to do is to import it. We’ll do this from a JavaScript file because by default webpack puts the CSS inside the bundle which means we have to reference it from another JavaScript file (we’ll look into how to extract the CSS to its own file later in this tutorial).

Put this in your index.js file:

import "./styles.css";

To be able to use CSS in your webpack app, you need to set up a new loader. Out-of-the-box, webpack only understands Javascript and JSON. With a loader, you can translate another type of file to a format that webpack understands and can work with.

You set up a loader with the module keyword in your webpack.config.js.

This is how you configure CSS in your webpack.config.js:

 module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  }

The test keyword tells webpack what kind of files should use this loader. The use keyword tells webpack which loaders should be run for these files.

As you can see in the config, you need to use two loaders, style-loader and css-loader. You also need to install them as NPM dependencies:

npm install --save-dev style-loader css-loader

Loaders are just pure JavaScript functions: they take some data as input and do something to that data and returns a transformed version of the data. When you use two loaders in webpack then it takes the output of the first and sends it as input to the second. In our example it takes the CSS file and runs it through css-loader then it takes the output and runs it as input to the style-loader

Let’s look at what the loaders do.

css-loader reads the CSS from the CSS file and returns the CSS with the import and url(...) resolved correctly. What does that mean? Let’s look at an example. Let’s say you have a url in the CSS referencing another resource, like an image:

.topbanner {
  background: url("topbanner.svg") #00D no-repeat fixed;
}

In this example, we reference topbanner.svg from the CSS. When css-loader sees this line, it tells webpack to load this file using an appropriate loader. For this to work you also need to configure a SVG loader, because the file is a SVG file:

module: {
   rules: [
    // CSS loader here
    {
       test: /\.svg$/,
       use: 'file-loader'
     }
   ]
}

If you wouldn’t have configured css-loader you would get an cryptic error like this:

ERROR in ./src/styles.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> .topbanner {
|   background: url("zip.svg") #00D no-repeat fixed;
| }
 @ ./src/styles.css 1:14-39
 @ ./src/index.js

You would get this error even though you had configured a loader for SVG files. Avoid weird erros like that by always using css-loader for your CSS configuration.

The next loader you configured was the style-loader. This loader adds the CSS to the DOM so that the styles are active and visible on the page. This is needed because the CSS is put into the bundle.js file - there is no separate styles.css file.

How to extract the CSS to its own styles.css file

It’s possible to output a separate styles.css file by using the mini-css-extract-plugin instead of using style-loader like we previously did.

The first thing you need to do is to install the dependency

npm install --save-dev mini-css-extract-plugin

Then you need to import the plugin at the top of the webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

Next thing is to enable the plugin in the plugin section of your webpack.config.js file:

plugins: [
  new MiniCssExtractPlugin(),
],

And last but not least, you replace the style-loader with MiniCssExtractPlugin.loader:

{
  test: /\.css$/,
  use: [
    MiniCssExtractPlugin.loader, // instead of style-loader
    'css-loader'
  ]
}

Now when you run webpack, it will output main.css file in the dist folder that you can reference from your index.html file. (remove the import "./styles.css"; line from index.js if you added that)

How to configure CSS modules in webpack

Now that you know how to configure pure CSS, let’s look into how to configure CSS modules.

But first what is CSS modules? From the github repo:

"A CSS Module is a CSS file in which all class names and animation names are scoped locally by default."

CSS is global - the classes you define can be used anywhere. But CSS modules are scoped to the component where they are used.

CSS-tricks.com has a good introduction to CSS Modules that is useful if you want to learn more. In this post, we’ll be focusing on learning how to configure it.

Configuring CSS modules is quite similar to configuring CSS.

{
  test: /\.css$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        importLoaders: 1,
        modules: true
      }
    }
  ]
}

you still use the css-loader and the style-loader. No extra dependencies are needed. But the difference to configuring CSS is that you add two options to css-loader.

It’s the modules: true that tells css-loader to enable CSS modules.

importLoaders: 1 means that it also applies CSS modules on @imported resources.

Using both CSS Modules and global CSS at the same time

So the way to enable CSS modules is to pass some options to the css-loader. But what if we want to use both CSS and CSS modules. Let’s say you have a layout.css file which is truely global CSS. Or you use a third-party component that is dependant on global CSS. It’s possible to do it!

We need a convention to use for deciding which files are global CSS and which are CSS modules. The way I like to do it is to use CSS modules only for files ending in *.module.css, for example modal.module.css, and all other *.css files are global.

To configure this we’ll add two loaders to our webpack config: one for CSS and one for CSS modules and we’ll use the include and exclude keywords for separating the two. include and exclude takes a regexp that decided if the file should be used or not. The complete setup looks like this:

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 1,
            modules: true
          }
        }
      ],
      include: /\.module\.css$/
    },
    {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader'
      ],
      exclude: /\.module\.css$/
    }
  ]
}

Now you can use both CSS and CSS modules in your project!


tags: