Juicer - a CSS and JavaScript packaging tool

For best performance, CSS and JavaScript should be served up using as few requests and bytes as possible. Juicer is a new command line tool that helps by resolving dependencies, merging and minifying files. It can even check your syntax, add cache busters to and cycle asset hosts on URLs in CSS files and more.

Belorussian translation.

Background

For web applications there's a screaming gap between development best practices, and deployment best practices. In order to achieve the best possible performance (to benefit both your users and your server) you should strive to require as few CSS and JavaScript files as possible on each page view - ideally only one of each. (Pulling in more files asynchronously is OK, since it doesn't drag down your initial rendering).

Developers generally prefer to work with some kind of source code organisation other than "one CSS file, one JavaScript file". Besides, inline documentation is hugely advantageous both for CSS and JavaScript code. With Juicer, you can organise your code any which way you want, and add in all the documentation you can come up with. When you're ready to go live, you can just do:

$ juicer merge stylesheets/main.css
Produced stylesheets/main.min.css from
  stylesheets/framework/reset.css
  stylesheets/framework/grid.css
  stylesheets/skins/midnight.css
  stylesheets/main.css

$ juicer merge javascripts/app.js
Produced javascripts/app.min.js from
  javascripts/prototype.js
  javascripts/lightbox.js
  javascripts/app.js

The resulting files will be minified by YUI Compressor, perfect for production environments.

Installation

In order to use Juicer, you need Ruby and RubyGems. If you want to use YUI Compressor for minification (or JsLint for JavaScript syntax verification) you'll also need Java on your system.

Once you've got your dependencies straight, you can install Juicer like so:

$ gem install juicer
$ juicer install yui_compressor
$ juicer install jslint

The two last lines will cause Juicer to download and unpack YUI Compressor, Rhino (Mozilla JavaScript engine written in Java) and JsLint.

Update

If you're having trouble installing Juicer using the apt provided version of Ruby, try the following:

sudo apt-get install build-essential
sudo apt-get install ruby1.8-dev

If you're using Ruby 1.9, substitute with ruby1.9-dev.

Now that you're up and running, let's look at juicer merge, the most important Juicer tool. In short it can merge and minify CSS and JavaScript files, but there's more to it than just that.

CSS files

Merging one or more CSS files is as easy as

juicer merge myfile.css myotherfile.css css/third.css

You can specify arbitrary many files, but one is often sufficient; Juicer can resolve dependencies and join all detected files.

Merging with dependencies

Dependencies are easy for CSS files; they already provide the @import directive. Juicer reads @import directives and replaces them with the contents of the file they reference in the generated output.

Cache busters

As explained in my recent post on the Expires header, cache busters are tokens added to a URL to make it unique, causing browsers to download the referenced content even if the original URL was already read and cached. Cache busters is what make a far future Expires header work in practice.

Juicer supports both kinds of cache busters I previously described:

$ juicer merge file.css

body {
    background: url(../images/bodybg.png?jcb=1234567890);
}

$ juicer merge -c hard file.css

body {
    background: url(../images/bodybg-1234567890.png);
}

For cache busters, Juicer adds the timestamp of the last time of edit on a file (aka mtime).

URLs

All relative URLs are handled relatively from the CSS file itself. If you use absolute URLs (ie /images/logo.png) in your CSS files, you need to let Juicer know what directory to handle those relatively from in order to add cache busters. This can be done by way of the -d ( --document-root) option.

You can also use --document-root in cooperation with --relative-urls or --absolute-urls in order to convert all URLs to either relative (shortest possible path from CSS file) or absolute URLs. Relative URLs in the resulting output file will always be relative to the output file. This means that:

$ juicer merge -o dist/ main.css

Will cause images/logo.png in the original file to be converted to ../images/logo.png in the generated file ( dist/main.min.css).

Asset hosts

Most browsers will only download 2 files in parallel from a single domain. For this reason, using 2 or 3 domains to deliver your static assets may speed up your site. Juicer supports this technique by allowing you to specify asset hosts and then use them evenly throughout your CSS:

$ juicer merge -h http://assets1.example.com,http://assets2.example.com main.css

This will cause about half the URLs in main.css to reference http://assets1.example.com and the other half http://assets2.example.com.

JavaScript files

For JavaScript files, Juicer does much the same. However, JavaScript doesn't have a dependency construct such as CSS' @import. In addition, Juicer offers to verify the quality of JavaScript code using JsLint. For instance, by default, Juicer won't minify files unless they pass JsLint. This can be overriden, but is generally a good idea since it helps you avoid problems caused by minification.

Merging with dependencies

Since JavaScript doesn't have an @import statement, Juicer brings it's own directive to the table, the @depends directive. It's real simple:

/**
 * @depends prototype.js
 * @depends widgets/lightbox.js
 */
(function(global) {
    // Your code here
})(this);

If you don't like the s, you can also use @depend. @depend/ @depends takes a path relative to the JavaScript file itself, and upon merging, gets replaced by the contents of that file.

Syntax verification

juicer merge uses JsLint for QA on your JavaScript files. If a warning is detected, Juicer aborts and tells you what's wrong. If you want to continue merging and minifying anyway (not recommended), you can pass Juicer the -i option:

$ juicer merge -i app.js
Verifying app.js with JsLint
  Problems detected
  Lint at line 15 character 2: Missing semicolon.
  }

Problems were detected during verification
Ignoring detected problems
Produced app.min.js from
  app.js

If you just want to check a file (and not merge/minify it and so on), you can invoke juicer verify app.js

Some thoughts on Sprockets

Those of you who pay attention to sites like ajaxian.com might realise that Juicer looks somewhat like Sprockets, very recently released by Sam Stephenson. Fact is, for JavaScript, Juicer is somewhat like Sprockets. I had no idea that was gonna surface, and honestly I think Sprockets is looking pretty cool.

Anyway, Sprockets has taken this all in a bit of another direction (or a bit further, depending). Sprockets does some things Juicer doesn't do, and Juicer does a few things Sprockets doesn't do. This is especially true for CSS files, which I don't think Sprockets touch at all.

Future plans

Much as the Sprockets project does, I had planned a Rails plugin which replaces the javascript_include_tag and stylesheet_link_tag helpers with Juicer enabled ones. I think it's important to treat the CSS files too, so I'll probably do this real soon.

Documentation generation is another feature I've been wanting to add to Juicer, like juicer doc, which can generate API documentation using JsDoc, YUI Doc and others.

I plan to add more minifiers too; Packer and JsMin are two obvious tools to add support for.

In addition there's some loose thoughts, but I'll get back to you when they're shaping up. In the meantime I hope you find Juicer useful, and I'd love feedback! Add a comment, fork me on Github, report a bug in Lighthouse, or send me an email (christian @ cjohansen.no).

Published 1. March 2009 in javascript, ruby, github og css.

Possibly related