FullHuman/purgecss-webpack-plugin

Contenthash with MiniCssExtractPlugin does not change even if content has changed

Opened this issue · 2 comments

I'm using the contenthash in the filenames to allow long term caching:
https://webpack.js.org/guides/caching/

Unfortunately if the content is changed by the PurgeCss plugin only, for example since I have removed a usage of an class or I'm using a class which hasn't been used before, the contenthash does not change. This leads to old files being served to the users and therefore a breaking app.

The content hash changes as soon as I do an actual modification of the CSS files. My guess would be that the contenthash is calculated to early?

webpack config:

const path = require('path');
const glob = require('glob-all')
const ManifestPlugin = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin'); //installed via npm
const PurgecssPlugin = require('purgecss-webpack-plugin');
const webpack = require('webpack');

// the path(s) that should be cleaned
let pathsToClean = [
    'dist'
];

// the clean options to use
let cleanOptions = {
    root:     '..................../public',
    verbose:  true,
    dry:      false
};

function collectWhitelistPatterns() {
    return [/^pcr-/];
}

module.exports = {

    plugins: [
        new CleanWebpackPlugin(pathsToClean, cleanOptions),
        new ManifestPlugin(),
        new PurgecssPlugin({
            paths: glob.sync(['./src-js/**/*', './templates/**/*'],  { nodir: true }),
            whitelistPatterns: collectWhitelistPatterns,
        }),
        new MiniCssExtractPlugin({
            filename: "[name]-[contenthash].css",
            chunkFilename: "[id]-[contenthash].css"
        }),
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        })
    ],

    entry: {
        one: './src-js/index-one.js',
        two: './src-js/index-two.js',
        three: './src-js/index-three.js',
    },

    output: {
        path: path.resolve(__dirname, 'public/dist'),
        filename: dev?'[name]-min.js':'[name]-[contenthash]-min.js',
        publicPath: "/dist/"
    },

    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        "presets": [
                            [
                                "@babel/preset-env",
                                {
                                    "useBuiltIns": "entry",
                                    "targets": "> 0.25%, not dead",
                                }
                            ]
                        ],
                        "plugins": ["transform-remove-console"]
                    }
                }
            },
            {
                test: /\.(sa|sc|c)ss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader',
                ],
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    },
    externals: {
        jquery: 'jQuery'
    },
};

Simply adding a comment to a stylesheet seems to be kicking off the re-hashing process. I'm just adding a comment like: /* cache increment = 1000 */

Not the best solution since it would be preferable not to have to think about it.

@arneee I just ran into this same problem today. Had to purge my Cloudflare cache to get an update to download after nearly an hour of troubleshooting. Putting immutable on your cache-control headers is fun when the hashes aren't working correctly...

My solution was to stop using this Webpack plugin, and move PurgeCSS to my postcss.config.js file instead (via @fullhuman/postcss-purgecss.

// postcss.config.js
/* eslint-env node */
const DEV = process.env.NODE_ENV !== 'production'

class TailwindExtractor {
  static extract(content) {
    return content.match(/[A-z0-9-:\/]+/g) || []
  }
}

module.exports = {
  plugins: [
    require('tailwindcss')('./tailwind.js'),
    DEV
      ? false
      : require('@fullhuman/postcss-purgecss')({
          content: ['./src/**/*.{js,ejs,html}'],
          extractors: [
            {
              extractor: TailwindExtractor,
              // Specify the file extensions to include when scanning for
              // class names.
              extensions: ['html', 'js', 'ejs', 'vue'],
            },
          ],
        }),
    require('autoprefixer'),
  ].filter(Boolean),
}

Webpack module config:

      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: { importLoaders: 1 },
          },
          { loader: 'postcss-loader' },
        ],
      },

Hope this helps someone!