Bootstrap 5 Remove Unused CSS with Webpack and PurgeCSS

By Priyash Patil | 5 min read | Updated: Oct 22, 2023 9PM UTC

When you use Bootstrap for your project, you usually end up with a lot of unused CSS. The full Bootstrap CSS bundle is around 227 KB which is quite heavy for most projects that only use a fraction of the available components. In this post, we’ll walk through how to set up a Bootstrap 5 project with Webpack and then use PurgeCSS to strip out the unused CSS, bringing the bundle size down to around 5.8 KB.

Prerequisites

  1. Node.js installed on your machine.
  2. Basic familiarity with npm and JavaScript build tools.

Setting up Bootstrap 5 with Webpack

First, let’s create a new project and install the required dependencies.

mkdir bs5-webpack && cd bs5-webpack
npm init -y

Install the dependencies:

npm install bootstrap @popperjs/core express compression
npm install --save-dev webpack webpack-cli webpack-dev-server autoprefixer css-loader css-minimizer-webpack-plugin html-webpack-plugin mini-css-extract-plugin postcss-loader sass sass-loader style-loader terser-webpack-plugin purgecss-webpack-plugin

Project Structure

Set up the following project structure:

bs5-webpack/
├── src/
│   ├── js/
│   │   └── main.js
│   ├── scss/
│   │   └── styles.scss
│   └── index.html
├── index.js
├── webpack.config.js
└── package.json

Import Bootstrap SCSS Selectively

Create src/scss/styles.scss and import only the Bootstrap components you need. This is the first step of optimization — only importing what’s required:

@import "bootstrap/scss/mixins/banner";
@include bsBanner("");

// Configuration
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";

// Layout & components
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/tables";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/dropdown";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/card";

// Helpers
@import "bootstrap/scss/helpers";

// Utilities
@import "bootstrap/scss/utilities/api";

Set up the JavaScript Entry Point

Create src/js/main.js to import the SCSS and any Bootstrap JS you need:

'use strict'

// Import our custom CSS
import '../scss/styles.scss'

// Import only the Bootstrap JS components you need
import { Dropdown } from 'bootstrap/js/src/dropdown';

Create the HTML Page

Create src/index.html with your page content. For this example, we’ll use the Bootstrap Pricing example template which includes cards, tables, navs, dropdowns, and a footer.

Configure Webpack

Create a webpack.config.js in the project root. This configuration handles SCSS compilation, CSS extraction, and minification:

'use strict'

const path = require('path')
const autoprefixer = require('autoprefixer')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const miniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: './src/js/main.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    devServer: {
        static: path.resolve(__dirname, 'dist'),
        port: 8080,
        hot: true
    },
    plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' }),
        new miniCssExtractPlugin(),
    ],
    module: {
        rules: [
            {
                test: /\.(scss)$/,
                use: [
                    { loader: miniCssExtractPlugin.loader },
                    { loader: 'css-loader' },
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [autoprefixer]
                            }
                        }
                    },
                    { loader: 'sass-loader' }
                ]
            }
        ]
    },
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({ terserOptions: { compress: {} } }),
            new CssMinimizerPlugin(),
        ],
    },
}

Set up Express for Testing

Create index.js in the project root to serve the built files with compression:

const express = require('express');
const compression = require('compression');

const app = express();
const port = 3000;

app.use(compression());
app.use(express.static('dist'));

app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

Update package.json Scripts

Add the following scripts to your package.json:

{
  "scripts": {
    "start": "webpack serve",
    "build": "webpack build --mode=production",
    "express": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

Build and Check Initial Bundle Size

With everything set up, run the following command to build your project and start the Express server:

npm run build && npm run express

Visit the page at http://localhost:3000, right-click, select “inspect,” and navigate to the network tab in the Inspector. Upon refreshing the page, you should see the main.css size, which should now be 31.5 KB.

Screenshot of main.css highlighting bundle size of 31.5 KB

Configure the PurgeCSS (with caution)

PurgeCSS is a valuable tool for eliminating unused CSS from your project. Before using using PurgeCSS make sure that you are very well aware of which classes and components are required. Because PurgeCSS will most likely remove some other state-related classes like dropdown states and animations.

PurgeCSS looks for a list of classes used in your HTML and based on that removes those that aren’t used. This is where you need to use a safelist to fine-tune things according to your usage. We will integrate PurgeCSS into Webpack using the purgecss-webpack-plugin. Follow these steps:

Install the plugin with the following command:

npm i --save-dev purgecss-webpack-plugin

Next, update your webpack.config.js with the provided changes. Below are the necessary code modifications:

const path = require('path')
const glob = require("glob");
const autoprefixer = require('autoprefixer')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const miniCssExtractPlugin = require('mini-css-extract-plugin')
const {PurgeCSSPlugin} = require("purgecss-webpack-plugin");

const PATHS = {
  src: path.join(__dirname, "src"),
};

module.exports = {
   // ... (existing configuration)
   plugins: [
     // ... (existing plugins)
     new miniCssExtractPlugin(),
     new PurgeCSSPlugin({
       paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true}),
       safelist: {
         deep: [/dropdown-menu$/]
       },
     })
   ],
   // ... (existing module rules)
}

Explanation of Changes:

  1. We’ve imported the glob library for efficient path handling.
  2. We’ve introduced the PurgeCSSPlugin and defined a PATHS object specifying the source directory.
  3. In the plugins section, we’ve added a new instance of PurgeCSSPlugin. This instance will search for CSS classes across the specified paths, and it includes a safelist rule for classes ending with “dropdown-menu.”

You may need to customize the safelist according to your specific needs. For more details, check the plugin’s documentation.

Finally, let’s run the build again to get the final bundle size: npm run build && npm run express. Then check the final bundle size in Inspector should be 5.8 KB.

Screenshot of browser inspector highlighting css bundle size of 5.8 KB

Conclusion

In conclusion, optimizing your Bootstrap project by removing unused CSS can significantly reduce your bundle size. By following the steps outlined in this post, you can achieve a much smaller production bundle. Initially, the CSS bundle size was around 227 KB, but after implementing PurgeCSS and the necessary configurations, the final bundle size was reduced to a lean 5.8 KB.

This level of optimization can greatly enhance the performance of your web application, ensuring that only the necessary styles are included in your production code. For more details and access to the complete source code, please refer to the GitHub repository provided.

Keep the Conversation Going

I hope you found this post helpful! If you have any questions or feedback, feel free to reach out. You can also find me on X (Twitter) @priyashpatil for additional insights and updates on my latest content.

Related

Featured on laravel-news.com and benjamincrozat.com.

Optimized image uploads with CKEditor and Laravel

By Priyash Patil on Friday, 03 November 2023

Bootstrap 5 Remove Unused CSS with Vite and PurgeCSS

By Priyash Patil on Wednesday, 17 January 2024

Laravel Vite Deploy Assets to Global CDN

By Priyash Patil on Friday, 19 January 2024

Laravel file upload with validation example

By Priyash Patil on Thursday, 25 January 2024