Optimising CSS and Javascript

By James Steel on

3 minutes | Performance | Kirby

Use Kirby to optimise CSS and Javascript

Javascript Minification

It's common knowledge that you should be bundling your JavaScript code into a single file, minified file. There are lots of ways to do this, with Webpack, Gulp, Grunt … the options are myriad. The trouble is, these can be tricky to configure, and Webpack is especially daunting.

The good news is, if you are using Slate and Slate Engine to build your project, you get this for free because its built on top of Laravel Mix which allows easy use of Webpack.

SASS Minification

Again, like Javascript, you really should be minifying your CSS for production sites. Slate Engine can help with this but we can go further. The truth is, there are whole bunch of CSS rules in your sheet that you haven't used.

This is particularly true if you have used a framework like Bootstrap or Foundation or something like Font Awesome. Slate tries its best to deliver just what you need and nothing more, but still there can be stray bits and pieces you can safely strip out.

Tools like CSSNano and UnCss can come to the rescue here, but they need some help. Laraval Mix uses CleanCSS, so that's covered already.

Let's setup Uncss and use Kirby to help out the process. With the PostCSS UnCss plugin installed, we can configure it in webpack.mix.js and package.json.

If you read the documentation for UnCss, it alludes to the fact that you need to feed it a list of html files to analyse in order to strip out unused css. That's fantastic, but this is a dynamic site, and we don't have any html files. How do solve this?

Kirby to the rescue. UnCss will accept a json array of pages to analyse. We can use a route in Kirby to generate the page list. Add the following to your Kirby config file:

c::set('routes', array(
  array(
    'pattern' => 'uncss.json',
    'action'  => function() {
      header('Content-type: application/json; charset=utf-8');
      $pages = site()->index()->visible();
      $json = array();
      foreach ($pages as $page) {
          $json[] = (string)$page->url();
      }
      echo json_encode($json);
    }
  )
));

Visiting /uncss.json will show you the jsonfor the whole site, formatted properly for UnCss. So far so good, but UnCss can't read a remote location. NPM & bash can help here. Add the following to your package.json config block:

"config": {
...
"uncssurl": "hashandsalt.salt/uncss",
...
},

Now we can use Curl to download the JSON to a file on disk:

"scripts": {
 ...
 "uncss": "curl $npm_package_config_uncssurl > uncss.json",
 ...
 },

All that's needed now its to feed this into Postcss via the mix.options settings in webpack.mix.js:

mix.options({
...
postCss: [
  require('postcss-uncss')({
  html: JSON.parse(require('fs').readFileSync('./uncss.json', 'utf-8')),
  ignore: ['#error', /.(uniform|error|valid|required|fa)/]
  }),
],
});

Note the ignore option. UnCss can't execute javascript when it assess your pages, so you need to feed it a list of ID's and classes that get dynamically added. Often you will have a lot of prefixed classes, particularly if you have used a plugin or two.

You don't have to declare them all, the above list applies to classes that contain that string. For example, if you have used Fontawesome, and you want UnCss to ignore all of the icons in the font, simply declaring fa in the ignore list is enough.

Now all you need to do is tweak your Mix production rule in package.json so that it grabs the json before minifying:

"production": "yarn uncss && cross-env NODE_ENV=production webpack --progress --hide-modules --config=$npm_package_config_webpack"

Conclusion

That worked wonders on the code for this site, reducing it's total weight by about 11k, which is quite a big win. You should see some quite dramatic results if you have used frameworks like Bootstrap or Foundation.