WittCode💻

Handling CSS with Webpack

By

Learn how to handle CSS with Webpack including bundling CSS into a CSS file and style tags. We will do this using the Webpack style-loader, css-loader, and MiniCSSExtractPlugin.

Table of Contents 📖

Webpack Loaders

To use CSS with Webpack, we need to use a loader. A loader is a function that transforms source code. For example, there are Webpack loaders that transform TypeScript to JavaScript, SCSS to CSS, etc. We do not need a loader for JavaScript as webpack knows JavaScript code out of the box. However, for other resources like TypeScript files and stylesheets we need to install a loader for them. Webpack loaders are available on npm such as ts-loader for TypeScript and css-loader for CSS.

CSS Loader

To work with CSS, we need to use a CSS loader. A CSS loader transforms CSS into a string and loads it into a JavaScript file. An example of a CSS loader is css-loader from npm. Lets install it as a development dependency.

npm i css-loader -D

Now lets configure webpack to use the css-loader package on CSS files.

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: `${__dirname}/dist`
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: 'css-loader'
      }
    ]
  }
}
  • The module key tells Webpack how different module types should be handled. A module is essentially a discrete chunk of code.
  • The rules key tells Webpack how it should handle each module. It accepts an array of rule objects.
  • A rule object tells webpack how to handle a certain module.
  • The test key is a test that must be passed for the loader to be ran. Here, the test says that the file must be a CSS file to be transformed by the CSS loader.
  • The use key accepts loader(s) to be applied to the files that pass the test.

Now lets add some CSS into our styles.css file.

h1 {
  color: #FFFFFF;
}

body {
  background-color: #272727;
}

Now lets import this CSS file into a JavaScript file.

import styles from '../public/styles.css';

Now build the bundle using webpack --config webpack.config.cjs.

webpack --config webpack.config.cjs

Now if we inspect the output file inside webpack's output directory we will see a lot of webpack magic, but we can also see our CSS outputted as a string.

c.push([e.id,"h1 {\n    color: #FFFFFF;\n}\n\nbody {\n    background-color: #272727;\n}",""]);

Style Loader

Now we need a way to get these styles to be displayed inside an HTML page. We can do this using another loader called style-loader. Install it from npm with npm i style-loader -D.

npm i style-loader -D

The style-loader takes the output from the css-loader and applies it to the DOM. Specifically, it takes the output from the css-loader and places it inside a <!-- webpack.config.cjs -->

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: `${__dirname}/dist`
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

Note that the order in the array supplied to use is very important. We want our css-loader to be ran before our style-loader because first our CSS is transformed into a string and placed inside the JavaScript bundle file by the css-loader. This output is then placed into a style tag by the style-loader. Loaders proivded to use are executed from last to first, so here the css-loader will be ran on the CSS files and then the style-loader will be ran next. Now lets create an HTML file called index.html inside webpack's output directory to house our bundle file.

touch dist/index.html

Now lets add an

tag and our output bundle script.

<!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>Document</title>
</head>
<body>
  <h1>WittCode is Cool!</h1>
  <script src="./bundle.js"></ script>
</body>
</html>

Now if we serve up this HTML file, we can see the imported styles inside the HTML head tag.

<style>
  h1 {
    color: #FFFFFF;
  }
  
  body {
    background-color: #272727;
  }
</style>

Creating CSS Files

Note that these styles are created by the bundle.js JavaScript file that webpack creates.

<script src="./bundle.js"></ script>

In other words, the style-loader injects these styles at runtime using JavaScript. If we want to include static files, we should use the plugin called MiniCSSExtractPlugin.

npm i mini-css-extract-plugin -D

A plugin is a feature of webpack that allows us to access, or plug in to, webpack's lifecycle. This allows us to do things such as configure global constants at compile time, monitor webpack's compilation progress, create HTML files to serve the bundles webpack creates, and more. The MiniCSSExtractPlugin creates a CSS file per JavaScript file that contains CSS. Now lets replace the style-loader with the MiniCSSExtractPlugin.

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: `${__dirname}/dist`,
    clean: true
  },
  plugins: [
    new MiniCssExtractPlugin(),
  ],
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  }
};

Now if we run webpack we will have a main.css file outputted to our bundle containing our CSS. Now we would just have to alter our HTML file to contain a link to this file.

h1 {
  color: #FFFFFF;
}

body {
  background-color: #272727;
}
<link rel="stylesheet" href="./main.css">

CSS Modules

Outputting the CSS to a style tag or a separate CSS file is useful but can lead to some issues with conflicts as it is global. To fix this, we can use CSS modules. CSS modules are scoped to the component that they are imported into, component being a React or Angular component. We can configure our css-loader to use modules.

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: `${__dirname}/dist`
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
            }
          }
        ]
      }
    ]
  }
}

The modules key tells the css-loader to use modules. Here we are enabling CSS modules for all files. To demonstrate, add a class to our styles.css file.

.cheese {
  background-color: orange;
  height: 50px;
  width: 50px;
}

Next, inside index.js, access this class, apply it to an element, and then add that element to the DOM body.

import styles from '../public/styles.css';

const myDiv = document.createElement('div');
myDiv.className = styles['cheese'];
document.body.appendChild(myDiv);

So, thanks to webpack loader customization, we can now access the cheese class from our CSS file in our JavaScript file. Now if we run webpack and look at our HTML we can see how this works.

<style>
  .m8JDw00U4tM39thlOrX1 {
    background-color: orange;
    height: 50px;
    width: 50px;
  }
</style>

<div class="m8JDw00U4tM39thlOrX1"></div>

Webpack has created a custom class name that will not collide with another. Even if it is in a global style tag.

Handling CSS with Webpack