Purgecss: Attribute selector with space is being remove

Created on 22 Apr 2020  路  5Comments  路  Source: FullHuman/purgecss

Attribute selectors with space are being removed like

Code

[class*=' icon-'],
[class^='icon-'] {
  font-family: 'icomoon' !important;
}

Expected Behaviour

[class*=' icon-'],
[class^='icon-'] {
  font-family: 'icomoon' !important;
}

Actual Behaviour

[class^='icon-'] {
  font-family: 'icomoon' !important;
}
bug

All 5 comments

Why closed? Still occurs!

@f-liva I can't remember why I closed it either

The issue is still alive, attribute selectors with space also trying to whitelist with patterns

I too have a similar issue some styles are removed while others aren't, actually using the webpack plugin with gulp to specify different purge whitelist files per entry.

gulpfile.ts

import { series, parallel, src, dest } from "gulp";

const rm = require("rimraf");
const imagemin = require("gulp-imagemin");
// const _ = require("lodash");
const fs = require("fs");
const glob = require("glob");
const webpack = require("webpack");
const prod = require("./webpack.prod");
const exec = require("child_process").exec;

const ASSETS_DIR: string = `./src/assets`;
const DIST: string = `./dist`;
const ENTRIES: Array<{
  [index: string]: string[];
}> = require("./deps").default;

/**
 * delete dist directory
 *
 * @param cb
 */
function clean(cb: () => void) {
  rm(DIST, cb);
}

/**
 * bundle npm + tailwind assets
 *
 * @param cb
 */

function bundle(cb: () => void) {
  let compiler = webpack(
    ENTRIES.map((entry) => {
      let config = prod(entry);
      // console.log(config);
      return config;
    })
  );

  compiler.run((err, stats) => {
    console.log(stats.toJson("verbose"));
    cb();
  });

  // compiler.run(cb);
}

/**
 * Update bundle.json with new file names
 *
 * @param cb
 */

function UpdateBundleInfo(cb: () => void) {
  glob(`${DIST}/assets/bundles/**/*.@(js|css)`, { nodir: true }, function (
    err,
    files
  ) {
    let bundle = files.reduce(
      (acc, file: string) => {
        if (file.match("/css/")) {
          // css file
          let radical = getRadical(file);
          acc.css[radical] = file.replace("./dist", "");
        } else {
          // js file
          let radical = getRadical(file);
          acc.js[radical] = file.replace("./dist", "");
        }
        return acc;
      },
      {
        css: {},
        js: {},
      }
    );

    fs.writeFile("./src/_data/bundle.json", JSON.stringify(bundle), cb);
  });
}

/**
 * Optimize images
 *
 * @param cb
 */
function optimizeImages() {
  return src("src/assets/img/**")
    .pipe(imagemin())
    .pipe(dest("dist/assets/img/"));
}

/**
 * Generate site
 *
 * @param cb
 */
function buildSite(cb: () => void) {
  exec("npm run build:11ty", cb);
}

/**
 * Run tests
 *
 * @param cb
 */
function test(cb: () => void) {
  cb();
}

/**
 * Run all finishing stuff
 *
 * @param cb
 */
function deploy(cb: () => void) {
  // delete public dir
  rm(`public`, function () {
    // rename dist to public
    fs.rename(DIST, "public", cb);
  });
}

function getRadical(file: string): string {
  return file.replace(/.+\/([^\/\\\.]+)\..+$/g, "$1");
}

function onError(error, cb) {
  // log error
  console.log(error);
  fs.writeFile("./log", JSON.stringify(error), function (err) {
    //restore dist.old to dist
    fs.rename(`${DIST}.old`, DIST, cb);
  });
}

exports.default = series(
  clean,
  parallel(
    series(bundle, UpdateBundleInfo, buildSite, test)
    // optimizeImages
  ),
  deploy
);

webpack.common.js

/* eslint-disable no-var, strict, prefer-arrow-callback */
"use strict";

var path = require("path");

module.exports = {
  entry: {
    index: "./src/assets/bundles/js/index.ts",
    article: "./src/assets/bundles/js/article.ts",
    about: "./src/assets/bundles/js/about.ts",
  },
  module: {
    rules: [{
        test: /\.ts(x?)$/,
        exclude: /node_modules/,
        use: [{
            loader: "babel-loader",
          },
          {
            loader: "ts-loader",
          },
        ],
      },
      {
        test: /\.js$/i,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
        }, ],
      },
      {
        test: /\.html$/i,
        use: [
          "file-loader?name=[name].[ext]",
          "extract-loader",
          {
            loader: "html-loader",
            options: {
              attributes: false,
              minimize: true,
            },
          },
        ],
      },
      {
        test: /\.pug$/i,
        use: [
          "file-loader?name=[name].[ext]",
          "extract-loader",
          {
            loader: "html-loader",
            options: {
              attributes: false,
              minimize: true,
            },
          },
          {
            loader: "pug-html-loader",
            options: {
              data: {},
            },
          },
        ],
      },
    ],
  },
  plugins: [],
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  output: {
    filename: "js/[name].js",
    path: path.resolve(__dirname, "dist/assets/bundles"),
  },
};

webpack.prod.js

const merge = require("webpack-merge");
const common = require("./webpack.common.js");
const glob = require("glob-all");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserJSPlugin = require("terser-webpack-plugin");
//const ImageminPlugin = require("imagemin-webpack-plugin").default;
//const imageminMozjpeg = require("imagemin-mozjpeg");
var path = require("path");
const PATHS = {
  src: path.join(__dirname, "src"),
};

module.exports = function (entry) {
  let fileName = Object.keys(entry)[0];
  let key = fileName.replace(/.+\/([^\/\\]+)\.[^\.]+$/g, '$1');
  return merge.smart(common, {
    mode: "production",
    entry: {
      [key]: fileName
    },
    output: {
      filename: "js/[name].[chunkhash].js",
    },
    optimization: {
      minimizer: [
        new TerserJSPlugin({}),
        new OptimizeCSSAssetsPlugin({
          cssProcessorPluginOptions: {
            preset: [
              "default",
              {
                discardComments: {
                  removeAll: true,
                },
              },
            ],
          },
        }),
      ],
    },
    module: {
      rules: [{
          test: /\.css$/i,
          use: [{
              loader: MiniCssExtractPlugin.loader,
            },
            {
              loader: "css-loader",
              options: {
                url: false,
              },
            },
            "postcss-loader",

          ],
        },
        {
          test: /\.s[ac]ss$/i,
          use: [{
              loader: MiniCssExtractPlugin.loader,
            },
            {
              loader: "css-loader",
              options: {
                url: false,
              },
            },
            "postcss-loader",

            // Compiles Sass to CSS
            'sass-loader',
          ],
        },
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: "css/[name].[contenthash].css",
        chunkFilename: "[id].css",
      }),

      new PurgecssPlugin({
        paths: glob.sync(console.log(Object.values(entry)[0]) || Object.values(entry)[0], {
          nodir: true
        }),
        whitelist: ["hide", "show"],
        whitelistPatterns: [],
        whitelistPatternsChildren: [],
        keyframes: true,
        fontFace: true,
        rejected: true,
        defaultExtractor: (content) =>
          content.match(/[\w-/:]+(?<!:)/g) || [],
        // defaultExtractor: content => {
        //   // Capture as liberally as possible, including things like `h-(screen-1.5)`
        //   const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []

        //   // Capture classes within other delimiters like .block(class="w-1/2") in Pug
        //   const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []

        //   return broadMatches.concat(innerMatches)
        // }
      }),

      new CompressionPlugin({
        filename: "[path].gz[query]",
        algorithm: "gzip",
        test: /\.js$|\.css$|\.html$/,
        minRatio: 1,
      }),

      new CompressionPlugin({
        filename: "[path].br[query]",
        algorithm: "brotliCompress",
        test: /\.(js|css|html|svg)$/,
        compressionOptions: {
          level: 11,
        },
        minRatio: 1,
      }),
    ],
  });

}

deps.js

var path = require("path");

exports.default = [{
        "./src/assets/bundles/js/index.ts": [
            "./src/index.njk",
            "./src/_includes/layouts/base.njk",
            "./src/_includes/contact.njk",
            "./src/_includes/footer.njk",
            "./src/_includes/navbar.njk",
            "./src/_includes/team.njk",
            "./src/_includes/objectives.njk",
            "./src/_includes/features_articles.njk",
            "./src/_includes/welcome.njk",
        ],
    },
    {
        "./src/assets/bundles/js/about.ts": [
            "./src/about.njk",
            "./src/_includes/layouts/base.njk",
            "./src/_includes/contact.njk",
            "./src/_includes/footer.njk",
            "./src/_includes/navbar.njk",
            "./src/_includes/team.njk",
            "./src/_includes/welcome.njk",
        ],
    },
    {
        "./src/assets/bundles/js/article.ts": [
            "./src/articles/**/*.njk",
            "./src/_includes/layouts/base.njk",
            "./src/_includes/layouts/article.njk",
            "./src/_includes/layouts/articles.njk",
            "./src/_includes/macros/embeds.njk",
            "./src/_includes/contact.njk",
            "./src/_includes/footer.njk",
            "./src/_includes/navbar.njk",
        ],
    },
]
// .map(entry => ({
//     [key = Object.keys(entry)[0]]: entry[key].map(filepath => path.resolve(__dirname, filepath))
// }));

this is the content of the src folder src.zip

I tried a little seperate test with purgecss itself and an unpurged tailwind generated css file ( dev build ) and I got the right ouput so think the issue is with the plugin beacause disabling also gives me the right output even for dev build

sorry finally found out it was an error in my webpack.common.js file where I defined entry points again

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Pab89 picture Pab89  路  8Comments

TomS- picture TomS-  路  3Comments

TylerBarnes picture TylerBarnes  路  5Comments

knowler picture knowler  路  3Comments

maartenvanbenthem picture maartenvanbenthem  路  4Comments