Dev Notes

Software Development Resources by David Egan.

Remove Unused CSS in WordPress


Bootstrap, CSS, Grunt, Tools, WordPress
David Egan

#Using Grunt Uncss with WordPress/Roots Building a site with the Bootstrap framework is a great way to take advantage of free open source styling.

CSS frameworks like Foundation and Bootstrap can’t be ignored - they are efficient collaborative efforts - and they only look “samey” when developers/designers get lazy.

When building a site with Bootstrap 3 under the Roots framework, you’ll typically have a minified CSS file of approximately 131kB. This is quite big, and the majority of styles won’t be used.

One option is to comment out the LESS modules that aren’t being used so that they are not compiled (no carousel? don’t compile carousel.less) but this is a blunt tool and still results in many CSS rules being loaded in the browser.

Enter UnCSS. This is a tool that removes unused CSS from stylesheets. it efficiently turns a sledgehammer into a precision instrument.

UnCSS had been turned into a Grunt module by Addy Osmani.

Though it has been designed to work with static sites, Liam Gladdy has developed a workaround for WordPress that works exceptionally well. There is a complete description of Liam’s method in this blog article. It involves using a WordPress plugin to build a JSON sitemap, and passing this to the UnCSS Grunt task. The UnCSS magic then checks each page of your dynamically generated site, and removes any unused style rules from the compiled CSS. This is an amazingly useful resource - and has the potential to give a significant performance boost.

We modified the Roots Gruntfile.js to manage all this - adding some minification for further savings.

Build the Sitemap

Create a mu-plugins directory in the wp-content directory of your development site, and add the JSON sitemap plugin.

You’ll need to autoload the mu plugin - create a file called load.php inside the mu-plugins dirtectory, and add the following line, referencing the correct path:

require WPMU_PLUGIN_DIR.'/JSON-Sitemap-Generator/grunt-plugin.php';

Make sure package.json includes the following lines before running sudo npm install:

"devDependencies": {
    "grunt": "~0.4.2",
    "grunt-contrib-clean": "~0.5.0",
    "grunt-contrib-cssmin": "^0.10.0",
    "grunt-contrib-jshint": "~0.6.4",
    "grunt-contrib-less": "~0.9.0",
    "grunt-contrib-uglify": "~0.2.4",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-exec": "^0.4.6",
    "grunt-uncss": "^0.3.6",
    "grunt-wp-version": "~0.1.0"
  }

Add an exec task to Gruntfile.js that builds the sitemap:

exec: {
      get_grunt_sitemap: {
	// the output of the show_sitemap() function (from the plugin) used to build sitemap.json
        command: 'curl --silent --output sitemap.json http://localhost/useful/?show_sitemap'
      }
    },

Create an UnCSS Grunt Task

UnCSS task, followed by a cssmin task to minify the output CSS file.

uncss: {
      dist: {
        options: {
          ignore       : [
          // needed for Bootstrap's transitions
          '.bs.carousel',
          '.slid.bs.carousel',
          '.slide.bs.carousel',
          '.fade',
          '.fade.in',
          '.collapse',
          '.collapse.in',
          '.collapsing',
          '.alert-danger',
          '.logged-in .navbar-default',
          '.carousel-inner > .next',
          '.carousel-inner > .prev',
          '.carousel-inner > .next',
          '.carousel-inner > .prev',
          '.carousel-inner > .next.left',
          '.carousel-inner > .prev.right',
          '.carousel-inner > .active.left',
          '.carousel-inner > .active.right'

          ],
          stylesheets  : ['assets/css/main.css'],
          ignoreSheets : [/fonts.googleapis/],
          urls         : [], //Overwritten in load_sitemap_and_uncss task
        },
        files: {
          'assets/css/main.css': ['**/*.php']
        }
      }
    },
cssmin: {
        options: {
          // cssmin has a problem with relative urls. Ref: http://stackoverflow.com/questions/23916622/how-to-rewrite-relative-url-in-minified-css-with-cssmin
          // set 'noAdvanced: true' to circumvent this
                keepSpecialComments: 0,
                //target: 'commonDir',
                noAdvanced: true,
                banner: '/* ======= David Egan, carawebs: http://carawebs.com =======*/'
            },
        css:{

          files: {
            "assets/css/main.min.css": ["assets/css/main.css"]
            }

          }
      }

Register Tasks

Register a task that loads sitemap.json. Add the UnCSS tasks to a “build” task:

grunt.registerTask('load_sitemap_json', function() {
    var sitemap_urls = grunt.file.readJSON('./sitemap.json');
    // pass the URLs to the uncss:dist task
    grunt.config.set('uncss.dist.options.urls', sitemap_urls);
  });

grunt.registerTask('deploy_build', [
  'clean',
  //'less',
  'less:deploy',
  'uglify',
  'exec:get_grunt_sitemap',
  'load_sitemap_json',
  'uncss:dist',
  'cssmin:css',
  'version'
  ]);

Entire Gruntfile, Roots 6.5

'use strict';
module.exports = function(grunt) {

  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        'assets/js/*.js',
        '!assets/js/scripts.min.js'
      ]
    },
    less: {
      dist: {
        files: {
          'assets/css/main.min.css': [
            'assets/less/app.less'
          ]
        },
        options: {
          compress: true,
          // LESS source map
          // To enable, set sourceMap to true and update sourceMapRootpath based on your install
          sourceMap: true,
          sourceMapFilename: 'assets/css/main.min.css.map',
          sourceMapRootpath: '/useful/wp-content/themes/useful-studio/' // '/useful' necessary cos site not in root of domain
        }
      },

      deploy: {
        files: {
          'assets/css/main.css': [
            'assets/less/app.less'
          ]
        },

      }
    },
    uglify: {
      dist: {
        files: {
          'assets/js/scripts.min.js': [
            'assets/js/plugins/bootstrap/transition.js',
            'assets/js/plugins/bootstrap/alert.js',
            'assets/js/plugins/bootstrap/button.js',
            'assets/js/plugins/bootstrap/carousel.js',
            'assets/js/plugins/bootstrap/collapse.js',
            'assets/js/plugins/bootstrap/dropdown.js',
            'assets/js/plugins/bootstrap/modal.js',
            'assets/js/plugins/bootstrap/tooltip.js',
            'assets/js/plugins/bootstrap/popover.js',
            'assets/js/plugins/bootstrap/scrollspy.js',
            'assets/js/plugins/bootstrap/tab.js',
            'assets/js/plugins/bootstrap/affix.js',
            //'assets/js/plugins/bootstrap-touch-carousel.js',// swipe support
            'assets/js/plugins/*.js',
            'assets/js/_*.js'
          ]
        },
        options: {
          // JS source map: to enable, uncomment the lines below and update sourceMappingURL based on your install
          // sourceMap: 'assets/js/scripts.min.js.map',
          // sourceMappingURL: '/app/themes/roots/assets/js/scripts.min.js.map'
        }
      }
    },
    version: {
      options: {
        file: 'lib/scripts.php',
        css: 'assets/css/main.min.css',
        cssHandle: 'roots_main',
        js: 'assets/js/scripts.min.js',
        jsHandle: 'roots_scripts'
      }
    },
    watch: {
      less: {
        files: [
          'assets/less/*.less',
          'assets/less/bootstrap/*.less'
        ],
        tasks: ['less', 'version']
      },
      js: {
        files: [
          '<%= jshint.all %>'
        ],
        tasks: ['jshint', 'uglify', 'version']
      },
      livereload: {
        // Browser live reloading
        // https://github.com/gruntjs/grunt-contrib-watch#live-reloading
        options: {
          livereload: false
        },
        files: [
          'assets/css/main.min.css',
          'assets/js/scripts.min.js',
          'templates/*.php',
          '*.php'
        ]
      }
    },
    clean: {
      dist: [
        'assets/css/main.min.css',
        'assets/js/scripts.min.js'
      ]
    },

    exec: {
      get_grunt_sitemap: {
        command: 'curl --silent --output sitemap.json http://localhost/useful/?show_sitemap'
      }
    },

    // Using uncss with WordPress:
    // https://gladdy.uk/blog/2014/04/13/using-uncss-and-grunt-uncss-with-wordpress/

    uncss: {
      dist: {
        options: {
          ignore       : [
          // needed for Bootstrap's transitions
          '.bs.carousel',
          '.slid.bs.carousel',
          '.slide.bs.carousel',
          '.fade',
          '.fade.in',
          '.collapse',
          '.collapse.in',
          '.collapsing',
          '.alert-danger',
          '.logged-in .navbar-default',
          '.carousel-inner > .next',
          '.carousel-inner > .prev',
          '.carousel-inner > .next',
          '.carousel-inner > .prev',
          '.carousel-inner > .next.left',
          '.carousel-inner > .prev.right',
          '.carousel-inner > .active.left',
          '.carousel-inner > .active.right'

          ],
          stylesheets  : ['assets/css/main.css'],
          ignoreSheets : [/fonts.googleapis/],
          urls         : [], //Overwritten in load_sitemap_and_uncss task
        },
        files: {
          'assets/css/main.css': ['**/*.php']
        }
      }
    },

    cssmin: {
        options: {
          // cssmin has a problem with relative urls. Ref: http://stackoverflow.com/questions/23916622/how-to-rewrite-relative-url-in-minified-css-with-cssmin
          // set 'noAdvanced: true' to circumvent this
                keepSpecialComments: 0,
                //target: 'commonDir',
                noAdvanced: true,
                banner: '/* ======= David Egan, carawebs: http://carawebs.com =======*/'
            },
        css:{

          files: {
            "assets/css/main.min.css": ["assets/css/main.css"]
            }

          }
      }

  });

  // Load tasks
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-wp-version');
  grunt.loadNpmTasks('grunt-exec');
  grunt.loadNpmTasks('grunt-uncss');
  grunt.loadNpmTasks('grunt-contrib-cssmin');

// Register tasks
grunt.registerTask('default', [
    'clean',
    'less',
    'uglify',
    'version'
  ]);


grunt.registerTask('load_sitemap_json', function() {
    var sitemap_urls = grunt.file.readJSON('./sitemap.json');
    grunt.config.set('uncss.dist.options.urls', sitemap_urls);
  });

grunt.registerTask('deploy_build', [
  'clean',
  //'less',
  'less:deploy',
  'uglify',
  'exec:get_grunt_sitemap',
  'load_sitemap_json',
  'uncss:dist',
  'cssmin:css',
  'version'
  ]);

grunt.registerTask('dev', [
    'watch'
  ]);

};

comments powered by Disqus