Using Stylus to Ease the Pain

13.02.2012

Let's be honest, while writing CSS is pretty straightforward, architecting it and maintaining it is a pain in the arse. Stylus is pretty good at numbing some of that pain, and there are some absolute nuggets that I don't think people are aware of.

Sty… whah?

Stylus is a brilliant tool. It's essentially a CSS pre-processor à la SASS and LESS, written for node. Coupled with Jade, it's the single reason I ditched the traditional stack and switched to node. Yeah, that good.

Setup

I use Express and have Stylus set up as a middleware layer. To use some of the features I describe in a minute, it requires a custom compile function, which looks like this:

function compile(str, path) {
  return stylus(str);
}

Which is then passed to the middleware setup like this:

app.use(stylus.middleware({
  src: __dirname + '/public',
  compile : compile
}));

Consolidate and compress your CSS

CSS has a great feature in @import, to encourage the use of smaller, more modular CSS files. However, we know minimising HTTP requests is the quickest win in page load speed and so CSS is often lumped in a huge 1000 loc+ files.

With Stylus, @import directives are parsed on the server, so that you can keep your code modular and legible without the HTTP overhead. See the docs for more information.

Compressing the CSS output is as simple as setting compress : true in the Stylus middleware, or calling return stylus(str).set('compress', true) in the custom compile function.

Never use a sprite again

I HATE sprites. They are a nightmare to maintain. With what I'm about to tell you, you will never have to use a sprite again. You're welcome.

Stylus has the ability to inline images referenced in the CSS as data URIs. Use it like so:

function compile(str, path) {
  return stylus(str)
            .define('url', stylus.url({
              paths : [__dirname + '/public'],
              limit : 10000
            }));
}

limit is the file size threshold (bytes); if the file is bigger than that, then the url is referenced as usual.

Although URI encoding images incurs a slight size overhead, the resulting CSS file can be gzipped.

This technique means you can reference as many images as you want in your stylesheet without worrying about creating extra HTTP requests.

Vendor Prefixes

Nib is a library for Stylus. Are you writing CSS like this?

body {
  background: -webkit-gradient(linear, left top, left bottom,
                    color-stop(0, #fff), color-stop(1, #000));
  background: -webkit-linear-gradient(top, #fff 0%, #000 100%);
  background: -moz-linear-gradient(top, #fff 0%, #000 100%);
  background: linear-gradient(top, #fff 0%, #000 100%);
}

STOP! Even if you have a shortcut in your editor to generate this, what happens if you want to change #000 to #ccc? Pretty painstaking. With nib, you can write this:

body
  background linear-gradient(top, white, black)

Nib jumps in and vendor-prefixes everything for you, so you don't have to worry about it. I literally only use it for that purpose, but it does lots of other useful things if you want it to.

To use nib with your custom compile function do this:

// Make sure you install it
// with `npm install nib`

var nib = require('nib');

function compile(str, path) {
  return stylus(str)
            .use(nib());
}

Lose the punctuation

One of my favourite features of Stylus is the syntax. You have the option to go completely punctuation-free: no semi-colons, no colons and no curly-braces, just whitespace and indentation.

Lots of CSS sticklers will obviously prefer regular CSS syntax (which Stylus allows. Hurray(!) for the sticklers), but my eyes and fingers thank me for it. You don't need those characters, they are for the compiler. Compare this simple reset:

*
  margin 0
  padding 0

vs.

* {
  margin: 0;
  padding: 0;
}

In the punctuation-free example, only the useful tokens (excl. whitespace) exist, of which there are five. In the punctuated example, there are six extraneous punctuation tokens. For me, it's more succinct and more clear, but I'll let you decide on that one.

All together now

Here is my complete custom compile function:

function compile(str, path) {
  return stylus(str)
    .define('url', stylus.url({
      paths : [__dirname + "/public"],
      limit : 10000
    }))
    .set('filename', path)
    .set('compress', true)
    .use(nib());
}

If you have any comments, catch me on twitter.