Remove Unused CSS from WordPress Automatically with Grunt

- 13 comments

Development,Grunt,Roots,Snippets,WordPress

Liam Gladdy recently authored this post about how to remove unused CSS using the grunt-uncss task and I wanted to implement it on the Roots theme (which comes pre-packaged with Grunt). When I succeeded, it managed to reduce my final stylesheet size from 159kb down to 20kb. That’s an 87% reduction!

I’m no expert when it comes to Grunt, but it’s not as scary as you might think. So let’s walkthrough how I did it, and if you spot something that can be done more efficiently then please leave a comment. Here we go!

Quick Overview

I am using the Roots theme; any defaults I mention are exclusive to it.

By default, when you grunt build your Roots project in preparation for deployment, this is what Grunt does:

  1. Runs jshint to check for JavaScript errors
  2. Compiles your LESS into CSS and compresses it into main.min.css
  3. Parses your main.min.css and adds vendor-prefixed CSS properties using the Can I Use database (via the grunt-autoprefixer task)
  4. Uglifies your JavaScript into scripts.min.js
  5. Builds and uglifies a Modernizr script on the fly according to the CSS and JS already created in steps 1-4
  6. Finally it uses grunt-wp-assets to create a hash which is then pulled into lib/scripts.php to prevent CSS and JS being cached in your browser

Here’s what we’re going to do:

  1. Disable CSS compression in step 2 above and intercept Grunt’s workflow from here (because I found that UnCSS works better when the input stylesheet is unminified)
  2. Generate a sitemap of all published WordPress pages/posts
  3. Use the sitemap to parse all WordPress pages/posts for only used CSS rule-sets
  4. Compress the result of step 3
  5. Run grunt-autoprefixer on the compressed and now clean CSS
  6. Resume its previous workflow at step 4 in the list above

Prepare WordPress

grunt-uncss essentially parses a list of URLs and stylesheets and compiles the CSS rule-sets which correspond to elements on those pages. Only CSS rule-sets that actually affect elements on your site will be included in your final CSS.

So, we need to give grunt-uncss a list of all published WordPress pages. As Liam’s post mentions, this is pretty easy. Instead of his suggestion of making a plugin, I just added his function along with a few tweaks to my lib/extras.php file. Here is what I added:

Now WordPress will output a list of all published pages or posts (even custom post types) when we go to http://example.dev/?show_sitemap. I added the check for WP_ENV in the if statement because I only want to display the sitemap if I’m in my development environment and I recommend you do the same. Now strap on your Grunt gloves because everything we do going forward is Grunt related.

Add and Install the Grunt Tasks We Need

We need to add two new Grunt tasks to our project:

  • grunt-exec which we’ll use to fetch our sitemap and tell grunt-uncss to use it; and
  • grunt-uncss itself

If you’re like me and up to this point you’ve only used Roots + Grunt with its default tasks, this can seem daunting but it’s actually quite simple. In the Roots theme root directory just open package.json and add our two new tasks to the devDependencies section. Here is my finished file:

If you’re wondering where I got the version numbers for the new tasks from, I just used the latest stable release from each task’s Github repository which you can easily find by appending /releases to the repo’s URL.

Now to actually install these tasks, open a terminal window and in your theme directory do $ npm install.

Register and Configure Our Grunt Tasks

Now we need to configure our Grunt tasks to “intercept” the current workflow at step 2 in the first list at the top of this post. I’m going to start in the order that it made the most sense to me. (NB, we’re not really intercepting them, we’re creating a new task that starts out much like the old with a few key differences.)

At the bottom of Gruntfile.js, just after the build task is registered, we need to register the two new tasks we installed:

Now let’s configure our new tasks. Our grunt-exec task will use cURL to retrieve our sitemap so after the jsFileList declaration I created a new variable containing my current localhost development domain as a string: var domain = 'http://example.dev/';

Then I added the grunt-exec task config:

Next we need to add the grunt-uncss config:

We’re almost done, we just need to add a few arguments to the default LESS task that Roots ships with to prevent Grunt from compressing our CSS before it runs UnCSS.

Tweak the Default LESS Task

Inside of our LESS task we already have less:dev and less:build, but the new tasks we registered will not use those, they will use less:build_uncss and less:compress, which we need to create now. Here is the complete LESS task after adding those new arguments:

This post is getting long but if you want to view my complete Gruntfile.js you can check it out here.

Execute Our New Task

Whew, now we should be squared away. In your theme directory you can run $ grunt build_uncss and Grunt will now remove unused styles from your WordPress site. This can take anywhere from 30 seconds to several minutes, depending on how many pages/posts you have on your site, but the results speak for themselves.

Warning

Not everyone needs to remove unused CSS from their WordPress site and not everyone should. In fact, this methodology has some serious shortcomings. For example, if you’ve added styles to any WordPress pages/posts that are drafts or to page elements not yet visible (like comments sections), then UnCSS will not “see” those areas and will remove the CSS for them; when they do become visible they could appear unstyled.

WooCommerce and other ecommerce plugins could also be problematic, especially with WooCommerce’s new endpoint structure. It’s probably trivial to add those endpoints to the sitemap but if your ecommerce shop is highly customized—say, with a dropdown shopping cart in the header/nav area—you might experience some unstyled elements unless you tell UnCSS to ignore specific selectors.

I also ran into issues with JavaScript triggered classes having their styles ignored, especially the Bootstrap responsive navbar classes, but I added them to the ignore list and that solved the issues (classes to ignore are .collapse.in and .collapsing).

Finally, when I ran this on a site that used Disqus for its comments, PhantomJS crashed and the resulting stylesheet visually crippled my entire project.


Comments

  1. Hi, Sorry for responding to an old post, but I was reading link. and I was wondering if you could help me here since that thread was closed?

    I’m not familiar with gulp yet. I was grunt and uncss together okay with WordPress in the past. Would it be possible for you to share an example of how to use ignore in the context of sage/gulp please?

    Thank you! 🙂

  2. Hey there Michael,

    thanks for the detailed write-up, appreciate it a lot. The only problem I am facing is that it covers Grunt & Roots and I am using Gulp with Sage. I thought there were only a few tweaks to be made but it turns out that this is way trickier…

    Did you perform the whole UnCSS-process with Sage as well? Or do you know where I can turn to? Will be posting over at discourse.roots.io as well but wanted to talk to you directly since you’ve done some specific research and obviously managed to get it done.

    Thanks!
    Henning

    1. Hi Henning,

      Thanks for your question! Unfortunately I haven’t had a chance (or much need) to update my Grunt posts for Sage + Gulp. Discourse is definitely the way to go for now, although as my need for update tweaks like these arise I’ll be sure to write them up!

  3. I’m getting this error when I use “grunt built_uncss” –

    Warning: Unable to parse “./sitemap.json” file (Unexpected token <).

  4. I get this error when running ‘grunt build_uncss’ :

    C:Program Files (x86)Amppswwwdukewp-contentthemesroots-master>grunt build_uncss
    Running "jshint:all" (jshint) task
    
      > 2 files lint free.
    
    Running "less:build_uncss" (less) task
    File assets/css/main.min.css created: 137.76 kB → 137.76 kB
    
    Running "exec:get_grunt_sitemap" (exec) task
    
      > 'curl' is not recognized as an internal or external command,
      > operable program or batch file.
      > Exited with code: 1.
      Warning: Task "exec:get_grunt_sitemap" failed. Use --force to continue.
    
    Aborted due to warnings.
    
    Execution Time (2014-08-29 18:07:17 UTC)
    jshint:all              107ms  ██████████ 10%
    less:build_uncss        954ms  ███████████████████████████████████████████████████████████████████████████████████████ 88%
    exec:get_grunt_sitemap   17ms  ██ 2%
    Total 1.1s
    
      1. Thanks. I got it working, unfortunately it seems to have broken a few things on the site such as the bootstrap drop down menu 🙁

        1. Yes, see my concluding section about that. You will need to figure out which CSS selectors are being removed and add them to the ignore array.

        2. Sorry forget that last comment, I was being a little slow, just had to add to the ignore classes. Great article, very helpful thank you 😀

Leave a Comment