robert hahn a darn good web developer

CSS Preprocessor

Update: Based on feedback on Daniel’s blog (which was, thankfully, quite positive), I tried to rework my solution using gm4 instead of cpp . If you’re new to this article, read it through, but then check the update where I illustrate how to use gm4 to process stylesheets.

I was checking out Reddit today and discovered a link to an article written by Daniel Read. In it, he made a lazyweb request for some kind of CSS “Precompiler”. I must confess I didn’t read his article that carefully, but I read enough to get really interested in the problem and promised to write up a solution.

I have a sneaking suspicion that he must have read the Håkon Wium Lie interview on Slashdot, only because both the interview and his post happened to come out on the same day. Anyway, in the interview, “spy der mann” asked:

I always wanted to have “included” substyles or “aliases” in my CSS definition, to save redundancy.

I’m sure this question has crossed the minds of many a web developer; it’s certainly crossed mine. Håkon’s responded by saying that “the downsides of aliases were considered more significant than the benefits.”

This naturally leads to David’s request – is there then a way to have a file with some aliases and somehow compile CSS files with those aliases? Much work would then be saved – the win would be even bigger for complicated sites with multiple CSS files.

Oh, yum. Low hanging fruit. Let me pick it.

My solution relies on a few tools already laying about: CPP, a unix command line, and Mac OS X’s launchd facility. Here’s how I tie them all together:

Using CPP

According to the Wikipedia article on cpp, the C-preprocessor (emphasis mine):

The C preprocessor (cpp) is the preprocessor for the C programming language. It is invoked by the compiler to handle directives such as #include, #define, and #if. Since the language of such directives is not strictly specific to the grammar of C, the preprocessor can also be invoked independently to process another type of file.

Sounds like a pretty good tool to use here. Let’s dig in. Here is a *.csse (as Daniel proposed to call it) file:

styles.csse:

#include "includes.csse" div { _no_margins_n_paddings; border-color: 1px solid _red; background-color: _tan; } div#foo { _m_and_p(2px,3em); }

So, styles.csse is the stylesheet we want to process, and as you can see in the div layer, we have some non-standard CSS rules in there, like no_margins_n_paddings . We also have a standard C-preprocessor #include declaration. Since it’s in a double-quoted string, the cpp command realizes that it has to look in the current working directory for this file. And here it is:

includes.csse:

#define _red #f00 #define _tan #cc8 #define _no_margins_n_paddings margin: 0; padding: 0 #define _m_and_p(_m,_p) margin: _m; padding: _p

In this file, I made up 4 defines; the first three act like variables - if it sees a standalone _red , for example, it’ll replace it with #f00 . The last #define defines a macro - if you call _m_and_p(2px,3px) it’ll take the arguments 2px and 3px and stuff them into the the definition value.

to parse styles.csse , all you have to do is type, in a terminal window,

cpp -P styles.csse styles.css

and it’ll do the rest of the work. Breaking down the command:

cpp is the C preprocessor command

is the C preprocessor command -P says to suppress any undesirable cpp output

says to suppress any undesirable cpp output styles.csse is the input file to be processed, and

is the input file to be processed, and styles.css is the output file.

Ok, so this is pretty cool. I can now preprocess *.csse files at will. Here’s the output:

styles.css:

div { margin: 0; padding: 0; border-color: 1px solid #f00; background-color: #cc8; } div#foo { margin: 2px; padding: 3em; }

Nice! Every time I make a change to a css file, I just run the cpp command and we’re done! Except for, I don’t want to do that kind of work. I’d rather have the computer generate the files for me automatically.

Processing Many CSS Files at Once

Before I get to that, I’ll probably want to build a scalable way to parse multiple css files. Enter the gen_css.sh script:

gen_css.sh

#! /bin/sh cpp -P styles.csse styles.css cpp -P test.csse test.css exit 0

Not much to it, really. I’m just using the shell script to parse each and every *.csse file that needs processing. If I created a new stylesheet for MSIE browsers, I’d add this line to the script after the test.css one:

cpp -P test.csse test.css

Ok, so that’s done. I can generate as many CSS files as required for my site. But it would sure be nice to do this automatically, otherwise, this crazy idea wouldn’t really last that long. I do my development on a Mac OS X box, and there’s a handy feature there that I’m going to use: launchd .

Automatically Generating CSS Files

On Mac OS X, launchd provides us with a way to run jobs, either at preset times (like cron ) or on demand. Josh Wisenbaker wrote up a pretty decent intro to using launchd , so I’m not going to explain it here. I will show you my launchd .plist file to illustrate how I worked the magic:

ca.roberthahn.csspp.plist

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>ca.roberthahn.csspp</string> <key>OnDemand</key> <true/> <key>Program</key> <string>gen_css.sh</string> <key>WatchPaths</key> <array> <string>/Users/rhahn/site/css</string> </array> <key>WorkingDirectory</key> <string>/Users/rhahn/site/css</string> </dict> </plist>

ok, so, in English, I set up my launchd file to run on demand – whenever a file changes in the directory (or directories) I’m watching (see WatchPaths ), the script gen_css.sh is going to be executed. To make gen_css.sh run smoothly, I also make a point of setting the working directory.

To use this file, save it encoded in UTF-8 to your ~/Libraries/LaunchAgents/ directory (create it as needed) as ca.roberthahn.csspp.plist and get it going in the terminal by running:

launchctl load ca.roberthahn.csspp.plist

in the LaunchAgents directory. If Something Goes Wrong, run this:

launchctl unload ca.roberthahn.csspp.plist

in the LaunchAgents directory. If you need to re-enable it again, make sure you open up that file and look near the top for this:

<key>Disabled</key> <true/>

It gets added to the .plist file when you run the unload command, and it will not run until that gets removed.

It Can’t be That Easy, can it?

There is one significant caveat with this: Given the syntax that the C-preprocessor uses, you can’t define raw id selectors like this:

#foo { ... }

because the preprocessor thinks of #foo as a potential directive, and dies with an error. There are a couple of things you can do:

You can make a habit of writing div#foo – that is, precede the selector by the element name it applies to. define a macro that generates the selector. adding #define id(i) #i to your includes.csse file will permit you to write id(foo) and have it generate #foo properly. If you’re really brave, use gm4 as the preprocessor. I’ve used it. I can’t say it’s my favourite tool to work with though.

Wrap-Up, and Other Considerations

Ok, so what have we got here? I outlined a method to use the C-preprocessor to generate CSS files, threw together a quick sh script to run multiple cpp commands as needed, and finally, whipped up a way to get the computer to generate the files for me on an on-demand basis.

Excepting the last step, you can do all this on any machine with a unix userland and cpp installed. I’m not a real Linux expert, but I’m inferring from the article on launchd that the Linux equivalent might be xinetd . If you want to write up a howto on using this, I’ll be happy to augment this article (or link to yours).

Finally, this article was written as a way to use CPP to generate stylesheets. However, there is absolutely nothing keeping you from #define ing and #include ing your HTML files or your JavaScript files. Several years ago, I’ve actually experimented with using CPP as a means to build web pages, and I hope to write up my experiences on that soon.

In the meantime, I hope that Daniel Read can get some use out of this.