0

Using the configuration below, I'm able to create a single HTML file containing all my code correctly, but lots of unused bootstrap classes in there.

Please note that I'm customising Bootstrap by importing SCSS files from node_modules into a main.scss file in code.

Where am I going wrong?

const path = require('path');
const {
  CleanWebpackPlugin
} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const {
  scss
} = require('svelte-preprocess');
const Dotenv = require('dotenv-webpack');
const PurgecssPlugin = require('purgecss-webpack-plugin');
const glob = require('glob');

const prod = process.env.NODE_ENV === 'production';

module.exports = {
  module: {
    rules: [{
        test: /\.svelte$/,
        exclude: /node_modules/,
        loader: 'svelte-loader',
        options: {
          emitCss: true,
          hotReload: false,
          preprocess: require('svelte-preprocess')([scss()]),
        },
      },
      {
        test: /\.(sa|sc|c)ss$/,
        exclude: /node_modules/,
        use: [{
            loader: prod ? MiniCssExtractPlugin.loader : 'style-loader', // inject CSS to page
          },
          {
            loader: 'css-loader', // translates CSS into CommonJS modules
          },
          {
            loader: 'postcss-loader', // Run post css actions
            options: {
              plugins: function() {
                // post css plugins, can be exported to postcss.config.js
                return [require('precss'), require('autoprefixer')];
              },
            },
          },
          {
            loader: 'sass-loader', // compiles Sass to CSS
          },
        ],
      },
    ],
  },
  resolve: {
    alias: {
      svelte: path.resolve('../../node_modules', 'svelte'),
    },
    extensions: ['.mjs', '.js', '.svelte'],
    mainFields: ['svelte', 'browser', 'module', 'main'],
  },
  optimization: {
    minimizer: [
      new OptimizeCssAssetsPlugin({
        cssProcessorOptions: {
          map: {
            inline: false
          }
        },
      }),
      new TerserPlugin(),
    ],
  },
  plugins: [
    new Dotenv(),
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      minify: prod ?
        {
          collapseWhitespace: true,
          removeComments: true,
          removeRedundantAttributes: true,
          removeScriptTypeAttributes: true,
          removeStyleLinkTypeAttributes: true,
          useShortDoctype: true,
        } :
        false,
      template: 'index.html',
      inlineSource: '.(js|css)$', // embed all javascript and css inline
    }),
    new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
    new PurgecssPlugin({
      paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, {
        nodir: true
      }),
    }),
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

Here's the single and only SCSS file I'm using in the project; it's imported in index.js and serves as the only styling source for the whole project:

//colors
$primary: #123099;

//body
$body-bg: #3123b7;

// Required
@import '~bootstrap/scss/functions';
@import '~bootstrap/scss/variables';
@import '~bootstrap/scss/mixins/border-radius';
@import '~bootstrap/scss/mixins/buttons';
@import '~bootstrap/scss/mixins/gradients';
@import '~bootstrap/scss/mixins/list-group';
@import '~bootstrap/scss/mixins/background-variant';
@import '~bootstrap/scss/mixins/forms';
@import '~bootstrap/scss/mixins/grid';
@import '~bootstrap/scss/mixins/grid-framework';
@import '~bootstrap/scss/mixins/box-shadow';
@import '~bootstrap/scss/mixins/transition';
@import '~bootstrap/scss/mixins/hover';
@import '~bootstrap/scss/mixins/size';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~bootstrap/scss/mixins/text-truncate';
@import '~bootstrap/scss/mixins/deprecate';
@import '~bootstrap/scss/mixins/text-hide';
@import '~bootstrap/scss/mixins/text-emphasis';
@import '~bootstrap/scss/vendor/rfs';
@import '~bootstrap/scss/reboot';

// Optional
@import '~bootstrap/scss/utilities/spacing';
@import '~bootstrap/scss/utilities/sizing';
@import '~bootstrap/scss/utilities/flex';
@import '~bootstrap/scss/utilities/borders';
@import '~bootstrap/scss/utilities/text';
@import '~bootstrap/scss/utilities/background';
@import '~bootstrap/scss/utilities/shadows';
@import '~bootstrap/scss/utilities/display';
@import '~bootstrap/scss/utilities/position';
@import '~bootstrap/scss/grid';
@import '~bootstrap/scss/card';
@import '~bootstrap/scss/forms';
@import '~bootstrap/scss/buttons';
@import '~bootstrap/scss/list-group';
@import '~bootstrap/scss/custom-forms';
@import '~bootstrap/scss/spinners';
Sammy
  • 3,395
  • 7
  • 49
  • 95

1 Answers1

4

You have to whitelist Bootstrap CSS file using purgecss-whitelister Purgecss plugin.

Install purgecss-whitelister using npm i -D purgecss-whitelister and then use whitelister on the Bootstrap CSS file you're importing usually bootstrap/dist/css/bootstrap.css:

const whitelister = require('purgecss-whitelister');
...

module.exports = {
  ...
  plugins: [
    ...
    new PurgecssPlugin({
      keyframes: false,
      paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, {
        nodir: true
      }),
      whitelist: whitelister('bootstrap/dist/css/bootstrap.css')
    }),
  ],
}

Then build and you'll have a bundle.css including only the Bootstrap CSS classes you're actually using. For example, using just container, btn and btn-primary Bootstrap classes, my bundle.css size got down to 4.84 KiB instead of 193 KiB before using purgecss-webpack-plugin with purgecss-whitelister.

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • Thank you, but I’m not actually using a Bootstrap CSS file that way; the CSS is being created from customized Bootstrap SCSS instead. – Sammy Apr 16 '20 at 05:58
  • 1
    You do not mention anywhere in your question (*nor title*) that this is about a **custom bootstrap SCSS**. People trying to help you do not have that clue and you should update your question accordingly regarding this information. Even so, did you try to whitelist that file using `purgecss-whitelister`? From which location are you trying to import that file? Is it from inside `node_modules` or your app `src` path? – Christos Lytras Apr 16 '20 at 07:26
  • 2
    I'm very sorry about that. Indeed I assumed it was implicitly understood since I included the SCSS configuration code. My bad, I will update the question accordingly and will try whitelisting and report back. Thank you. – Sammy Apr 16 '20 at 07:30
  • The rule `test: /\.(sa|sc|c)ss$/` includes CSS as SASS and SCSS with each specific loader; it's not obvious at all that you have a customized bootstrap file regarding your problem. Also please update with the `scss` bootstrap file paths and how did you customize it (*e.g. changing class name prefixes*). – Christos Lytras Apr 16 '20 at 07:36
  • Done. I hope it's clear now. What I do not understand from your answer is which file to whitelist exactly in this case; my `main.scss` file? – Sammy Apr 16 '20 at 07:58
  • I'll look at it to see the nature of using this regarding PurgeCSS compatibility, these are lot of files and you should try just one if you want to test it with the `purgecss-whitelister` plugin to see if it's working. May I ask why are you doing it this way? Since you'll use PurgeCSS to purge unused CSS code, why don't you just import the whole Bootstrap code and let PurgeCSS strip any unused CSS? – Christos Lytras Apr 16 '20 at 08:18
  • Well, because I need to customize the Bootstrap variables first to adapt it to my theme before using it. If I just use the CSS as is, it will be the default Bootstrap theme. – Sammy Apr 16 '20 at 08:26
  • Ah yes, it makes total sense. I'll try to figure this out and will get back. – Christos Lytras Apr 16 '20 at 08:32
  • I can't reproduce the issue. I have copied the contents of your SCSS file and have the exact Webpack plugins and rules and `PurgecssPlugin` plugin does generate a `bundle.css` of `2.25 KiB` having your custom color variables applied. You can see it if you clone and build a demo project I've uploaded here https://github.com/clytras/svelte-bootstrap-csspurge – Christos Lytras Apr 16 '20 at 10:06