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:
- Runs
jshint
to check for JavaScript errors - Compiles your LESS into CSS and compresses it into
main.min.css
- Parses your
main.min.css
and adds vendor-prefixed CSS properties using the Can I Use database (via thegrunt-autoprefixer
task) - Uglifies your JavaScript into
scripts.min.js
- Builds and uglifies a Modernizr script on the fly according to the CSS and JS already created in steps 1-4
- Finally it uses
grunt-wp-assets
to create a hash which is then pulled intolib/scripts.php
to prevent CSS and JS being cached in your browser
Here’s what we’re going to do:
- 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)
- Generate a sitemap of all published WordPress pages/posts
- Use the sitemap to parse all WordPress pages/posts for only used CSS rule-sets
- Compress the result of step 3
- Run
grunt-autoprefixer
on the compressed and now clean CSS - 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 tellgrunt-uncss
to use it; andgrunt-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.
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! 🙂
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
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!
PS, you could always do a quick search for Gulp wrappers of the modules I used. A quick search turned up
gulp-uncss
.Sweet! Worked like a charm! Thanks for this.
I’m getting this error when I use “grunt built_uncss” –
Warning: Unable to parse “./sitemap.json” file (Unexpected token <).
Sorry ignore my comment. I wasn’t in WP_ENV, Silly me haha
I get this error when running ‘grunt build_uncss’ :
Hi James, it looks like you’re running Windows and you don’t have Curl installed which is a requirement for this setup. Choose the version that matches your setup here and install it: http://curl.haxx.se/download.html#Win32
Thanks. I got it working, unfortunately it seems to have broken a few things on the site such as the bootstrap drop down menu 🙁
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.
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 😀
Glad you got it working and glad you enjoyed the article!