Update: Based on feedback on Daniel’s blog (which was, thankfully, quite positive), I tried to rework my solution using
gm4
instead ofcpp
. If you’re new to this article, read it through, but then check the update where I illustrate how to usegm4
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:
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:
#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:
#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-P
says to suppress any undesirable cpp output styles.csse
is the input file to be processed, andstyles.css
is the output file.Ok, so this is pretty cool. I can now preprocess *.csse
files at will. Here’s the output:
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.
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:
#! /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
.
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:
<?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.
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:
div#foo
– that is, precede the selector by the element name it applies to.#define id(i) #i
to your includes.csse
file will permit you to write id(foo)
and have it generate #foo
properly.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.
See Part 2, where I modify this exercise to show how to use gm4
to process stylesheets.
Copyright © 2009
Robert Hahn.
All Rights Reserved unless otherwise indicated.