Recently I’ve been working on a project which is using LESS to preprocess its CSS. The mockup design I was given requires image rollovers—which for the sake of having the rollover image ready and loaded—calls for the use of CSS sprites.

The Problem

I’m lazy. Well, not that lazy. Writing the sprite CSS is trivial, but creating the image can be problematic. Creating the original sprite is not too much work, but after subsequent changes maintaining a sprite image costs time and I want automation.

The Solution

The first solution I found is to use Compass, which supports spriting out of the box. Compass is based on SASS and since I’m not using SASS, I don’t consider this an option. I will, however, keep this in mind when starting my next project.

The next option I discovered is SmartSprites which works great on its own, but it requires annotations—CSS comments that resemble the following:

/** sprite-ref: mysprite; */

The Problem Part II

I am using Twitter recess to process my LESS files.

Since they are just comments this shouldn’t be an issue, but for some reason recess seems to be inconsistently removing these comments with no basis for why.

The Real Solution

I decided to do some preprocessing of my own. This solution requires SmartSprites, so go download it now.

I created my own “spec” for a new CSS property that I called background-sprite; it accepts all of the variables that the the annotations from smartsprites would normally use but with a different syntax, which was gracefully accepted by recess. Here’s a sample of the LESS code to do it:

/* First, declare the sprite normally which strangely works without issues */
/** sprite: broadcaster; sprite-image: url('../img/broadcaster-sprite.png'); sprite-layout: vertical; */

.channels li a {
    /* then inside a css rule: */
    background-sprite: url('../img/channel.png') sprite-ref(broadcaster) sprite-alignment(left);
}

Of course this is only half of the problem, because now we need to convert background-sprite properties into background-image properties with the appropriate trailing comment on the same line.

I created a python script (pysprite):

#!/usr/bin/env python

'''
preprocesses less-generated CSS files to convert background-sprite: to
annotated background-image:
'''

import sys
import re

for line in sys.stdin.readlines():
    sprite_regex = r'background-sprite:\s*.+(url\(.+?\)) (.+)'
    match = re.search(sprite_regex, line)
    if match:
        params = match.group(2)
        print 'background-image: %s; /** %s **/' % (
            match.group(1),
            params.replace('(', ': ').replace(')', ';').replace(';;', ';')
        )
    else:
        print line.rstrip()

and a Rakefile:

less_path='path/to/less_files'

desc 'Generates regular CSS (dev mode)'
task :css do
  sh "recess #{less_path}/app.less --compile|pysprite > #{less_path}/../app.css"
  sh "rm webroot/sprite.php"
end

desc 'Generates sprite CSS'
task :sprites do
  sh "recess #{less_path}/app.less --compile|pysprite > #{less_path}/../app.css && smartsprites --root-dir-path #{less_path}/../."
  sh "echo \"<?php \\$app_css = 'app-sprite.css' ?>\" > webroot/sprite.php"
end

to chain everything together. Since I don’t want to manually run rake every time I make changes, I then set my rake css task to run every time I save a LESS file in vim, using the following autocmd:

au BufWritePost *.less silent !rake css

Now since I might not always be using sprites, rather than throwing this autocmd into my vimrc, I set it at runtime from the command line with an edit script I wrote, which sits in my repository root directory, right above the public webroot:

#!/bin/bash
mvim . -c "au BufWritePost *.less silent !rake css"

Note on linux you would change mvim to gvim or just plain vim if you prefer.

You will also notice I create and remove a sprite.php file. I use this file to dynamically set the stylesheet based on the last ran task so that I am not wasting time recreating a sprite image every time I save a file.

This means that during development I use regular image rollovers and when the site is ready for production I just have to run a rake sprites before uploading, which can easily be chained into a deployment process.

Troubleshooting

For everything to work, each one of these scripts needs to be executable (chmod u+x) and in your $PATH.

Getting smartsprites to work outside its own path took a bit of finagling in the smartsprites.sh script:

#!/bin/sh

#
# Add extra JVM options here
#
OPTS="-Xms64m -Xmx256m"

LINKPATH=$(dirname $(which $0))
SCRIPTPATH=$LINKPATH/$(dirname $(readlink $(which $0)))

java $OPTS -Djava.ext.dirs=$SCRIPTPATH/lib org.carrot2.labs.smartsprites.SmartSprites "$@"

Of course the quickest way to fix it if this modification not work is to change the -Djava.ext.dirs= to point to the absolute path of the smartsprites/lib directory.

I also symlinked it to smartsprites (without the “sh”).

Conclusion

It may seem a bit excessive, but with the added simplicity of vim’s autocmd, it becomes a very elegant solution to use, once implemented.

If you are still on the fence about LESS vs SASS and you know there may be image sprites in your future, to keep things straight forward you might want to opt for a SASS and Compass based approach.


blog comments powered by Disqus