Hulya Karakaya A frontend developer interested in open source and building amazing websites. I believe in building through collaboration and contribution.

How to configure CSS Modules for webpack

3 min read 1100

As your application or website grows larger, it becomes increasingly difficult to work with just one CSS file. This increase in size can lead to loads of problems, like trying to have different selector names, and scrolling up and down to find and edit a single selector in a huge file.

With CSS Modules, you can write your CSS rules traditionally, but styles are consumed with JavaScript and scoped locally to avoid unintended side effects elsewhere. This is achieved by creating a unique class name for CSS selectors, allowing you to use the same CSS class name in different files without worrying about name collisions. Plus, you don’t need to come up with different selector names, giving you complete flexibility and reusability of CSS within components.

In short, CSS Modules are component-based stylesheets that allow us to create contained, modular CSS by creating unique classes and scoped selectors.

In this article, we will practice using CSS Modules with a webpack demo project in order to learn how to configure an app to escape CSS’s notoriously tricky global scope problem.

Setting up webpack

Let’s begin by setting up webpack. Our demo app has an src folder containing index.html, style.css, and index.js.

Outside the src folder, we have our webpack.config.js, babel.config.js, package.json, and package-lock.json files.

Screenshot of CSS module file tree

You can use the npm run build command to build the project and npm run dev to start the app in localhost 8080.

Now, in the package.json file, we should have webpack, webpack-cli ,webpack-dev-server, html-webpack-plugin installed.

babel-related modules are for transforming modern JavaScript into an older syntax, and CleanWebpackPlugin will delete the contents of the dist folder every time the project is built.

We made a custom demo for .
No really. Click here to check it out.

For the webpack.config.js file, we have some configurations written like so:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  entry: {
    main: "./src/index.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
    publicPath: "",
  },
  target: ["web", "es5"],
  stats: { children: true },
  mode: "development",
  devServer: {
    static: path.resolve(__dirname, "./dist"),
    compress: true,
    port: 8080,
    open: true,
  },
  devtool: "inline-source-map",
  module: {
    rules: [
      {
        test: /\\.js$/,
        loader: "babel-loader",
        exclude: "/node_modules/",
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
    new CleanWebpackPlugin(),
  ],
}; 

In order to work with CSS Modules, we need to install style-loader and css-loader:

npm i css-loader --save-dev
npm i style-loader --save-dev

We need the css-loader module to interpret @import and url() like import/require(), and resolve them, along with the style-loader module to inject our CSS into the DOM.

Setting up style-loader and css-loader

We have babel-loader already set up in our rules array; this is the place for adding our loaders in webpack.

Loaders tell webpack how to modify files before they are added to the dependency graph. The rules array consists of our loaders, and helps us perform transformations on files. These help with the loading of files and images.

Note that we can chain multiple loaders together. In the following code block, css-loader and style-loader are used together.

Similar to babel-loader, we can load CSS files to style our pages like so:

module: {
    rules: [
      {
        test: /\\.js$/,
        loader: "babel-loader",
        exclude: "/node_modules/",
      },
     // CSS rules
      {
        test: /\\.css$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 1,
              modules: true,
            },
          },
        ],
      },
    ],
  },

After babel-loader, we have several other objects that will look for any CSS files and transform them:

  • The test key tells webpack to apply this rule to any file ending with the .css extension
  • The importLoaders option is given a value of 1, which sets the number of loaders applied before CSS Modules and the @import at-rule
  • Finally, the modules:true option enables CSS Modules

Creating an HTML file

Inside the HTML, we have a div with a class name of element. We will access this element inside our JavaScript file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CSS Modules Webpack</title>
  </head>
  <body>
    <div class="element"></div>
  </body>
</html>

Creating a CSS file

In the src folder, we have style.css file. Let’s add some CSS inside it:

:global body {
  margin: 0;
  padding: 0;
}

.page {
  background-color: purple;
  width: 100vw;
  height: 100vh;
  font-family: "Helvetica Neue", Arial, sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
}

.text {
  text-transoform: capitalize;
  color: #fff;
  font-weight: bold;
  font-size: 4em;
}

As you can see, we have our styles for the body selector and two other class names.

You may recognize that we have :global before the body selector. This will allow us to write global styles for the body selector.

We don’t need to add a link tag in the index.html; instead, we will import our style.css inside the JavaScript file:

// index.js

import styles from "./style.css";

console.log(styles);

Importing CSS files in JavaScript wouldn’t be possible without webpack. Once we connect css-loader, webpack will be able to work with this import and bring our CSS files into the bundle.

So, to begin to understand CSS modules, let’s begin by first looking at this import declaration: import styles from './style.css';.

Let’s look at what we get from the styles object by console logging:

The styles object in index.js

Our page and text class names will be compiled into random strings of letters, numbers, and characters. Based on this, we can refer to our classes with styles.page and styles.text.

So, we get the ease of use of referring to simple classes while maintaining the benefits of non-global CSS. This will add the generated class names to our import statement, and we can then utilize the style object, which refers to the generated classNames:

const element = document.querySelector(".element");

element.innerHTML = `<div class="${styles.page}">
     <p class="${styles.text}">CSS Modules Webpack</p>
   </div>`;

Now, the npm run build command builds a bundled version of our project in the dist folder.

Running npm run dev will show our styles applied to the page.

Basic app with CSS Modules Webpack written in white on a purple background

We can see the generated class names in the DOM tree.

Screenshot of DOM tree

Conclusion

In this article, we learned how to use CSS Modules with webpack. I have used vanilla JS, but you can use CSS Modules with Gatsby, React, and Next.js, too.

Writing modular styles has gained importance in web development communities, and different approaches are emerging that are similar to CSS Modules. One of them is CSS-in-JS, or styled-components. With this, you can write CSS directly inside your JavaScript files.

You can find this demo project on GitHub!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web apps — .

.
Hulya Karakaya A frontend developer interested in open source and building amazing websites. I believe in building through collaboration and contribution.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

Leave a Reply