Setting up CSS and Sass with Webpack!!

One of the most fundamental parts of frontend development is styling, in this post, we will see how to use styles with webpack. This post is a continuation of my earlier post where I have explained How to set up react project with webpack and babel. So if you have never set up a react project with webpack then I'll suggest you read my previous post and then come back to this post or happen to have a react project already set up and wonder how to work with styles then you are in the right place.

In this post, we will see how to set up both CSS and a CSS pre-processor like Sass with webpack one by one.

As we know webpack is a module bundler and it is responsible for analyzing the files and bundles all your need to run the application in a single JavaScript output file which we commonly call bundle.js. Webpack by default only understands javascript and in order to make webpack understand other resources like .css, .scss, etc we need the help of loaders to compile it. Let see how to do it.

Setting up CSS with webpack

At first, let's install some dev dependencies.

npm i --save-dev css-loader style-loader
  • The definition says css-loader interprets @import and url() like import/require() and will resolve them. What do we mean by this? css-loader will take all the CSS from the CSS file and generate it to a single string and will pass this to style-loader.
  • style-loader will take this string and will embed it in the style tag in index.html.

Now let's add the configuration in webpack.config.js. Add this line inside rules in the module.

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

Here the test property will tell the webpack to use style-loader and css-loader for all the .css files.

(Note: The order in which webpack apply loaders is from last to first, so as said earlier the css-loader will generate the output string which will be used by the style-loader.)

The overall content of webpack.config.js will be:

const path = require('path');

module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, './src/index.js'),
    devtool: "eval-source-map",
    module: {
        rules: [
          {
             test: /\.(js|jsx)$/,
             exclude: /node_modules/,
             use: ['babel-loader']
          },
          {
             test: /\.(css)$/,
             use: ['style-loader','css-loader']
          }
        ]
    },
    resolve: {
        extensions: ['*', '.js', '.jsx']
    },
    output: {
        path: path.resolve(__dirname, './public'),
        filename: 'bundle.js',
    },
    devServer: {
        contentBase: path.resolve(__dirname, './public'),
        hot: true
    },
};

Next, we will add an app.css file with the following content.

.container {
    height: 100vh;
    background-color: #E7E3EB;
}

.header {
    padding: 15px 20px;
    background-color: #6200EE;
}

h1 {
    color: #FFF;
}

App.jsx

import React from 'react';
import './app.css';

const App = () => {
    return (
        <div className="container">
            <div className="header">
                <h1>Welcome to React application</h1>
            </div>
        </div>
    )
}

export default App

and now run npm start and you'll see the output in your browser.

Screenshot 2021-04-28 at 6.51.19 PM

Configuration for your production environment.

If you have a webpack configuration for production then you'll need a different configuration for using CSS. At first, install mini-css-extract-plugin

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

and now extract the miniCssExtractPlugin and replace the style-loader with MiniCssExtractPlugin.loader and add the MiniCssExtractPlugin in plugin.
MiniCssExtractPlugin extracts CSS and create a CSS file per JS file.

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

module.exports = {
    mode: "production",
    entry: path.resolve(__dirname, './src/index.js'),
    devtool: "source-map",
    module: {
        rules: [
          {
             test: /\.(js|jsx)$/,
             exclude: /node_modules/,
             use: ['babel-loader']
          },
          {
             test: /\.(css)$/,
             use: [MiniCssExtractPlugin.loader,'css-loader']
          }
        ]
    },
    resolve: {
        extensions: ['*', '.js', '.jsx']
    },
    plugins: [new MiniCssExtractPlugin()],
    output: {
        path: path.resolve(__dirname, './public'),
        filename: 'bundle.js',
    },
};

Now, run npm run build and you will see the external files generated in your public folder like main.css and main.css.map. If you want to check whether your build folder has everything as expected you can check it on by running it on the local web server, run this command on your command line.

npx http-server public

This will provide you a URL that you can visit in a browser.


Setting up Sass with webpack

If you happen to prefer Sass more than CSS like me then you need to install some packages to set up Sass with webpack.

npm i --save-dev sass-loader node-sass
  • node-sass provides binding Node.js to LibSass (The C version of Sass). sass-loader document says "The sass-loader requires you to install either Node Sass or Dart Sass on your own. This allows you to control the versions of all your dependencies, and to choose which Sass implementation to use." Essentially this loader has internal dependencies which require node-sass.
  • sass-loader loads a Sass/SCSS file and compiles it to CSS.

Now let's update the webpack.config.js.

const path = require('path');

module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, './src/index.js'),
    devtool: "eval-source-map",
    module: {
        rules: [
          {
             test: /\.(js|jsx)$/,
             exclude: /node_modules/,
             use: ['babel-loader']
          },
          {
             test: /\.(s(a|c)ss)$/,
             use: ['style-loader','css-loader', 'sass-loader']
          }
        ]
    },
    resolve: {
        extensions: ['*', '.js', '.jsx']
    },
    output: {
        path: path.resolve(__dirname, './public'),
        filename: 'bundle.js',
    },
    devServer: {
        contentBase: path.resolve(__dirname, './public'),
        hot: true
    },
};

We just need to add the sass-loader ahead of css-loader, so now first, the .scss file compiles back to CSS and after that process remains the same as explained above.
Next, let's change the app.css file to app.scss and update the file with our favorite sass features.

app.scss

.container {
    height: 100vh;
    background-color: #E7E3EB;
    .header {
        padding: 15px 20px;
        background-color: #6200EE;
        h1 {
            color: #FFF;
        }
    }
}

Now run npm start the result will be the same as above but now we have written Sass in place of CSS.

Sass configuration for production environment

Configuration for production remains pretty same as we did for CSS setup, we will be using the same mini-css-extract-plugin to extract our separate CSS file, it is just that we need to add sass-loader ahead of css-loader as we did earlier for our development configuration. The updated file will look like this.

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

module.exports = {
    mode: "production",
    entry: path.resolve(__dirname, './src/index.js'),
    devtool: "source-map",
    module: {
        rules: [
          {
             test: /\.(js|jsx)$/,
             exclude: /node_modules/,
             use: ['babel-loader']
          },
          {
             test: /\.(s(a|c)ss)$/,
             use: [MiniCssExtractPlugin.loader,'css-loader', 'sass-loader']
          }
        ]
    },
    resolve: {
        extensions: ['*', '.js', '.jsx']
    },
    plugins: [new MiniCssExtractPlugin()],
    output: {
        path: path.resolve(__dirname, './public'),
        filename: 'bundle.js',
    },
};

Now build your project again by running npm run build and check the output by running in the local webserver.


Extras

Congratulations!! 🎉 You have now successfully set up CSS and Sass with webpack in your react project but your project needs a bit more than styles to make it more attractive and user friendly like images and fonts. I am will give a short tutorial on how to set them up with webpack.

Let's add a dev dependency to support these file formats.

npm i --save-dev url-loader

Font Setup

First, download fonts, for this tutorial purpose I have downloaded Open Sans Italic from google fonts and moved them in a folder (src/Assets/Fonts/). Now create a font.scss file and add the font face inside

@font-face {
    font-family: 'Open Sans Italic';
    src: url('./Assets/Fonts/OpenSansItalic.eot');
    src: url('./Assets/Fonts/OpenSansItalic.eot?#iefix') format('embedded-opentype'),
         url('./Assets/Fonts/OpenSansItalic.woff') format('woff'),
         url('./Assets/Fonts/OpenSansItalic.ttf') format('truetype'),
         url('./Assets/Fonts/OpenSansItalic.svg#OpenSansItalic') format('svg');
    font-weight: normal;
    font-style: italic;
}

and let's import the fonts file inside app.scss and apply the fonts to <h1> tag.

app.scss

@import url(./font.scss);

.container {
    height: 100vh;
    background-color: #E7E3EB;
    .header {
        padding: 15px 20px;
        background-color: #6200EE;
        h1 {
            color: #FFF;
            font-family: 'Open Sans Italic';
        }
    }
}

Next, update the webpack.config.js to support all the file formats.

...
   rules: [
          ....
          {
                test: /\.(woff|woff2|eot|ttf|svg)$/,
                use: {
                  loader: 'url-loader',
                },
          },
          ....
          ]
...

Now, run npm start and now your font will be updated.

Image setup

To use images with webpack we just need to add the image file extension which we need inside the test of url-loader.
Updated webpack.config.js

...
   rules: [
          ....
          {
                test: /\.(woff|woff2|eot|ttf|svg|jpg|png)$/,
                use: {
                  loader: 'url-loader',
                },
          },
          ....
          ]
...

Now, let's update the app.jsx and app.scss.

App.jsx

import React from 'react';
import './app.scss';

const App = () => {
    return (
        <div className="container">
            <div className="header">
                    <h1>Welcome to React application</h1>
            </div>
            <div className="react-logo"></div>
        </div>
    )
}

export default App

app.scss

@import url(./font.scss);

.container {
    height: 100vh;
    background-color: #E7E3EB;
    .header {
        padding: 15px 20px;
        background-color: #6200EE;
        h1 {
            color: #FFF;
            font-family: 'Open Sans Italic';
        }
    }

    .react-logo {
        background: url('./Assets/Images/react-logo.png') no-repeat center;
        height: 500px;
        width: 500px;
        background-size: contain;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }
}

Now let's run the npm start again and see the output in the browser.

Screenshot 2021-04-29 at 2.06.17 AM

As we can see our image has been added and the fonts have been updated.
There are plenty of options for all the loaders we have used in this tutorial, I suggest you read the documents and use them according to your project needs or you can just explore them also.

Don't forget to give a ❤️ if you liked it and thanks for reading and if you want to support me then you can just buy me a coffee 😃

Happy coding! 🧑🏻‍💻