Remove unused CSS. Also works with single-page apps.

  • By PurifyCSS
  • Last update: Jan 5, 2023
  • Comments: 14

PurifyCSS

Travis npm David Join the chat at https://gitter.im/purifycss/purifycss

A function that takes content (HTML/JS/PHP/etc) and CSS, and returns only the used CSS.
PurifyCSS does not modify the original CSS files. You can write to a new file, like minification.
If your application is using a CSS framework, this is especially useful as many selectors are often unused.

Potential reduction

  • Bootstrap file: ~140k
  • App using ~40% of selectors.
  • Minified: ~117k
  • Purified + Minified: ~35k

Usage

Standalone

Installation

npm i -D purify-css
import purify from "purify-css"
const purify = require("purify-css")

let content = ""
let css = ""
let options = {
    output: "filepath/output.css"
}
purify(content, css, options)

Build Time

CLI Usage

$ npm install -g purify-css
$ purifycss -h

purifycss <css> <content> [option]

Options:
  -m, --min        Minify CSS                         [boolean] [default: false]
  -o, --out        Filepath to write purified css to                    [string]
  -i, --info       Logs info on how much css was removed
                                                      [boolean] [default: false]
  -r, --rejected   Logs the CSS rules that were removed
                                                      [boolean] [default: false]
  -w, --whitelist  List of classes that should not be removed
                                                           [array] [default: []]
  -h, --help       Show help                                           [boolean]
  -v, --version    Show version number                                 [boolean]

How it works

Used selector detection

Statically analyzes your code to pick up which selectors are used.
But will it catch all of the cases?

Let's start off simple.

Detecting the use of: button-active

  <!-- html -->
  <!-- class directly on element -->
  <div class="button-active">click</div>
  // javascript
  // Anytime your class name is together in your files, it will find it.
  $(button).addClass('button-active');

Now let's get crazy.

Detecting the use of: button-active

  // Can detect if class is split.
  var half = 'button-';
  $(button).addClass(half + 'active');

  // Can detect if class is joined.
  var dynamicClass = ['button', 'active'].join('-');
  $(button).addClass(dynamicClass);

  // Can detect various more ways, including all Javascript frameworks.
  // A React example.
  var classes = classNames({
    'button-active': this.state.buttonActive
  });

  return (
    <button className={classes}>Submit</button>;
  );

Examples

Example with source strings
var content = '<button class="button-active"> Login </button>';
var css = '.button-active { color: green; }   .unused-class { display: block; }';

console.log(purify(content, css));

logs out:

.button-active { color: green; }
Example with glob file patterns + writing to a file
var content = ['**/src/js/*.js', '**/src/html/*.html'];
var css = ['**/src/css/*.css'];

var options = {
  // Will write purified CSS to this file.
  output: './dist/purified.css'
};

purify(content, css, options);
Example with both glob file patterns and source strings + minify + logging rejected selectors
var content = ['**/src/js/*.js', '**/src/html/*.html'];
var css = '.button-active { color: green; } .unused-class { display: block; }';

var options = {
  output: './dist/purified.css',

  // Will minify CSS code in addition to purify.
  minify: true,

  // Logs out removed selectors.
  rejected: true
};

purify(content, css, options);

logs out:

.unused-class
Example with callback
var content = ['**/src/js/*.js', '**/src/html/*.html'];
var css = ['**/src/css/*.css'];

purify(content, css, function (purifiedResult) {
  console.log(purifiedResult);
});
Example with callback + options
var content = ['**/src/js/*.js', '**/src/html/*.html'];
var css = ['**/src/css/*.css'];

var options = {
  minify: true
};

purify(content, css, options, function (purifiedAndMinifiedResult) {
  console.log(purifiedAndMinifiedResult);
});

API in depth

// Four possible arguments.
purify(content, css, options, callback);
The content argument
Type: Array or String

Array of glob file patterns to the files to search through for used classes (HTML, JS, PHP, ERB, Templates, anything that uses CSS selectors).

String of content to look at for used classes.


The css argument
Type: Array or String

Array of glob file patterns to the CSS files you want to filter.

String of CSS to purify.


The (optional) options argument
Type: Object
Properties of options object:
  • minify: Set to true to minify. Default: false.

  • output: Filepath to write purified CSS to. Returns raw string if false. Default: false.

  • info: Logs info on how much CSS was removed if true. Default: false.

  • rejected: Logs the CSS rules that were removed if true. Default: false.

  • whitelist Array of selectors to always leave in. Ex. ['button-active', '*modal*'] this will leave any selector that includes modal in it and selectors that match button-active. (wrapping the string with *'s, leaves all selectors that include it)

The (optional) callback argument
Type: Function

A function that will receive the purified CSS as it's argument.

Example of callback use
purify(content, css, options, function(purifiedCSS){
  console.log(purifiedCSS, ' is the result of purify');
});
Example of callback without options
purify(content, css, function(purifiedCSS){
  console.log('callback without options and received', purifiedCSS);
});
Example CLI Usage
$ purifycss src/css/main.css src/css/bootstrap.css src/js/main.js --min --info --out src/dist/index.css

This will concat both main.css and bootstrap.css and purify it by looking at what CSS selectors were used inside of main.js. It will then write the result to dist/index.css

The --min flag minifies the result.

The --info flag will print this to stdout:

    ________________________________________________
    |
    |   PurifyCSS has reduced the file size by ~ 33.8%
    |
    ________________________________________________

The CLI currently does not support file patterns.

Github

https://github.com/purifycss/purifycss

Comments(14)

  • 1

    Builds broken -- Uglifyjs & new release

    Hi,

    Sorry for opening an issue. Didn't know any other way to contact you. I am the owner of Quasar Framework and I depend on this package on Quasar starter kit. Due to the deprecation with error on Uglifyjs devs cannot build anymore.

    But I saw this commit https://github.com/purifycss/purifycss/commit/108142998959b1812365e69c6229e0c0284adedc and was wondering when an npm release with this change will be available so I can take a decision: either remove purifycss from the build or wait for the npm release.

    Thank you and sorry for opening a github issue for this (but times are hard), Razvan

  • 2

    Adding options.strict to allow more aggressive removal of unused selectors

    Support for calculated class names is nice, but it can allow unused classes to remain. When options.strict is set to true, selectors will be matched fully against full words including '_'. '-', and numbers.

  • 3

    breaking change?

    I am using grunt-purifycss which of course has never changed. However, a few days ago the version of purifycss used with it was 1.0.21 and is now 1.1.3. Somewhere in in between purifycss is now removing styles that are actually used. Are you aware of any breaking changes or something I need to do differently with the latest release? Thanks. Here is an example of something that was removed:

    html body header h1 button.expandMenu{
    
  • 4

    1.1.0

    The API will stay exactly the same. Rework is much simpler and will allow for more precise selector elimination + leaving the order of the CSS files the same (currently, it orders the CSS files into selectors/at-rules)

    The following issues should be fixed once this is merged in: #91 #85 #53

    Going to be adding stronger tests before I merge this in for v1.1.0-beta

  • 5

    Tool removes @font-face declarations :(

    Is there a way to prevent that? For everything else works great just my @font-face declarations are stripped away from the end-result :(

    Maybe a way to filter them?

    Thanks a lot

  • 6

    AngularJS classes that aren't used immediately get removed

    I understand that this could be a doozie to solve, but AngularJS does not add classes to certain elements until they are required to be animated, which is when it adds a whole bunch of classes (.ng-animate, .ng-enter, etc.). These get removed when the CSS gets purified.

    Also, angular doesn't add elements to the DOM that have an ng-if directive until a certain condition is met.

    Perhaps there can be some type of whitelist of classes to never remove from a source css document?

    Just a thought...

  • 7

    Please publish 1.2.6 to NPM (currently: 1.1.9)

    Hello! In trying to install purifycss I realized part of the issues I was encountering were because it's stuck at version 1.1.9, depending on the deprecated uglifyjs (without the dash), etc. Could you please publish your latest release? Thank you!

  • 8

    Pseudo elements not removed - fix not implemented

    Hi. I have noticed that many css selectors are not removed, including css using pseudo selectors (such as .glyphicon-asterisk:before {...}), although a fix does exist.

    Some info...

    • I am using purifycss via npm install purify-css - version 1.0.17. This does not remove pseudo selectors.
    • The master branch on github is actually a different version: version 1.0.8. This also does not remove pseudo selectors (although it looks like there may have been an attempt to merge the following fix).
    • Fix: https://github.com/purifycss/purifycss/issues/39 - I replaced my src/sccTree.js with this fix: https://github.com/purifycss/purifycss/blob/f45537378a8c236bd4ddca5f89d762b3e627ce23/src/cssTree.js and pseudo selectors are now removed successfully. Can this be worked back into the master and can the npm installer make use of this latest version?
    • In my setup, purify-css is reducing file size by 1.3 times but there are still many Bootstrap styles that remain (and are not found in any of my source templates); such as .progress-bar. Anybody else had this?

    Thanks

  • 9

    Option for removing css comments

    I ran my code through purifycss, and it gave impressive results. However, I noticed that the css comments contained in the original files were preserved. Is there an option for removing these?

  • 10

    SyntaxError when purifying with Django templates

    In the upgrade from 1.2.2 -> 1.2.3 I started getting the following errors:

    Unexpected token (1:1)
    SyntaxError
    

    when attempting to purify html files that are django templates.

    The templates look like this:

    {% extends "base.html" %}
    {% load hyperbola_core_tags imagekit %}
    
    {% block active_app %} contact {% endblock active_app %}
    {% block title %} {{ block.super }} :: Contact {% endblock title %}
    {% block section_title %} Contact Information {% endblock section_title %}
    
    {% block content %}
    <div class="row">
      <div class="col-md-9">
        <h1>{{ name|escape }}</h1>
        {% for contact in contacts %}
        {% ifchanged contact.type %}
        <h2>{{ contact.type.type|escape }}</h2>
        {% endifchanged %}
        <div class="row">
    // snip ...
    

    You can see the error in action by running gulp in this project: https://github.com/lopopolo/hyperbola (you might have to muck with deps)

  • 11

    Using prepack for js file

    Purifycss is relatively simple to understand. It takes a content file, like an html file and extract all the words from it. It takes a css file and extracts all the selectors from it. Then, it compares the two.
    But this approach causes some issues for more complex situation:

    const class1 = ["abc", "efg"].join("d")
    
    .abcdefg { background: blue; }
    

    In the above example, purifycss will extract the following strings from the js:

    • class1
    • abc
    • efg
    • join
    • d

    That means that abcdefg is not considered used since it will not match with the array of strings that we extracted from the js.

    Prepack will transform the js before extracting strings from it.

    const class1 = ["abc", "efg"].join("d")
    // becomes
    const class1 = "abcdefg"
    
  • 12

    Invalid CSS output if css has ";" symbol in imported URL

    I have Google Fonts on my CSS:

    @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,[email protected],500;1,500&amp;family=Oswald:[email protected];500&amp;family=Rambla:[email protected];700&amp;display=swap'); But output CSS converts it to:

    @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,[email protected],500;

    As the result output CSS file is corrupted and the browser is not processing it.

    Need to ignore when ";" symbol is using in the string.

  • 13

    undefined (at undefined:undefined): "undefined" while purifying

    I get this error(undefined (at undefined:undefined): "undefined" while purifying) everytime i try to check my wordpress site css on https://purifycss.online/. What is the problem?

  • 14

    purifycss not working on php exec

    ` $purifyCSS = "purifycss %s %s --info --out %s";

    $match = "/usr/share/nginx/example/media/css_html/main.html";

    $input = "/usr/share/nginx/example/media/css_html/input.css";

    $output = "/usr/share/nginx/example/media/css_html/ouput.css";

    exec(sprintf($purifyCSS , $input , $match, $output),$result);`