supercharge your css code with m4

css has vastly improved the quality of html markup on the web. however, given its complexity, it has some astounding deficiencies.

one of the biggest problems is the lack of constants. how many times have you wanted to code something like this? light_grey = #CCC. instead you are forced to repeat #CCC in your css. this quickly creates difficult-to-maintain and difficult-to-read code.

an elegant solution to the problem is to use a general purpose preprocessor like m4. m4 gives you a full range of preprocessing capability, from simple constants to sophisticated macros.

traditional css

consider the following example css:

.codeblock code { font:95% monospace; color: #444;}
div.codeblock {
   padding: 10px;
   border: 1px solid #888;
}
applying some m4 macros to this code, not only makes the css more maintainable, it also makes it more readable by increasing its semantic quality.

below, we add constants: mid_grey, dark_grey and std_padding.

the same css with m4

.codeblock code { font:95% monospace; color: dark_grey;}
div.codeblock {
   padding: std_padding;
   border: 1px solid mid_grey;
}

trying it it out

if you'd like to give this a try, m4 is usually available as a standard package e.g. on debian style linux flavors do:
# apt-get install m4 m4-doc
now copy the following code into a file called example.css
changequote(^,^)dnl                change quotes to something safe
changecom(^/*^, ^*/^)dnl           change comments to css style

define(dark_grey, ^#444^)dnl       define a dark grey color
define(mid_grey, ^#888^)dnl        define a middle grey color
define(std_padding, ^10px^)dnl     define the standard padding

.codeblock code { font:95% monospace; color: dark_grey;}
div.codeblock {
   padding: std_padding;
   border: 1px solid mid_grey;
}
and now run the preprocessor:
$ m4 example.css
you should see output like:
.codeblock code { font:95% monospace; color: #444;}
div.codeblock {
   padding: 10px;
   border: 1px solid #888;
}
notes on the example:
  • dnl tells m4 to "discard to next line" i.e. ignore everything after it. useful for comments.
  • i use changequote and changecom to change quoting and commenting characters to be more css compliant than the defaults.

using include statements

in practice, you'll often want to use your definitions in several css files. to do this, place your definitions into an external file. in our example, we split our code into two files, definitions.m4 and example.css as follows:

definitions.m4

define(dark_grey, ^#444^)dnl       define a dark grey color
define(mid_grey, ^#888^)dnl        define a middle grey color
define(std_padding, ^10px^)dnl     define the standard margin

example.css

changequote(^,^)dnl                change quotes to something safe
changecom(^/*^, ^*/^)dnl           change comments to css style
include(^definitions.m4^)dnl       include the definitions file

.codeblock code {font:95% monospace; color: dark_grey;}
div.codeblock {
   padding: std_padding;
   border: 1px solid mid_grey;
}

note: my choice of filenames and extensions (definitions.m4, example.css) is arbitrary.

once you've split the files, you can run the preprocessor as before:

$ m4 example.css

further thoughts

this article describes a tiny subset of the power of the m4 language. for more information, take a look at the gnu manual.

one thing that i don't discuss here is integrating a preprocessor into your development / build environment. more on that later.

tech blog

if you found this article useful, and you are interested in other articles on linux, drupal, scaling, performance and LAMP applications, consider subscribing to my technical blog.

This seems overly complex,

This seems overly complex, not to mention that (as noted) css is built to alleviate the need to do this sort of thing.

As it stands, the reason for using this would be to solve this issue:

div.codeblock { padding: std_padding;}
div.otherblock { padding: std_padding;}
div.yetanotherblock { padding: std_padding;}

in so that you can have the same padding througout without needing to change several instances. CSS simply allows you to do this:

div.codeblock, div.otherblock, div.yetanotherblock { padding: 5px; }

And you've accomplished the same thing. Obviously, coding like the second example is a bit more difficult and requires more planning, but you end up with optimized, faster loading (smaller file) CSS than if you're simply doing (what ends up amounting to) 'find and replace' in the css.

Perhaps m4 has more benefit to it than that -- why why not php then?

jeff, seasons greetings and

jeff, seasons greetings and thanks for your comments. while i agree with your points:

  1. css should be normalized to the extent possible
  2. css file sizes should be kept as small as possible e.g. by minimizing repetition

and agree that neither of these two things require a preprocessor.

i think you've missed my two central points i.e.

  1. the semantic quality of css code can be increased by defining meaningful constants and expressions e.g. "light_grey=#ccc" or "main_panel_width=600px" or even "side_panel_width=main_panel_width - 200px". this is not possible with plain-old-css but is possible with a preprocessor.
  2. the maintainability of css code can be increased by not repeatedly implicitly defining e.g. light grey to be #ccc or the width of your main panel to be 600px. again, this simply can't be done with regular css. the example you give is a particular but a not a general solution.

In reading back I may have

In reading back I may have come across on the terse side, unintentionally I assure you. I don't browse the internet looking to pick fights :)

Your points are valid, even if I can't justify the extra effort needed for this myself. And, should I find the need, my preference would probably lay within doing this via PHP and not "yet another scripting language" :)

jeff, agreed. the links that

jeff, agreed.

the links that dennison post below offer an interesting alternative for those using php. this has the advantage, as you point out, of not introducing another language. it also doesn't require an extra build step.

it's worth noting that m4 isn't a scripting langage, but rather a simple preprocessor. preprocessors are typically used to complement languages, rather than offer a competing language, e.g. the relationship between CPP and the C/C++ languages familiar to a lot of programmers.

the preprocessor approach has the advantage of not introducing any run-time overhead, and being language and platform independent.

thanks again for bringing up the php idea and to dennison for the links.

I agree, this can be done so

I agree, this can be done so easily in PHP which is also a widely supported scripting language, so you will have no problems with hosting. Check out the following links: http://sperling.com/examples/pcss/ and http://www.digital-web.com/articles/generating_dynamic_css_with_php/

Maybe it's because I haven't

Maybe it's because I haven't seen enough of what m4 can do, but it just seems like extra work that CSS already does. If I had a stylesheet that I thought was hard to read or update, I'd rearrange things so that colors and padding, for example, would be specified in as few places as possible. I like the fact that you can have more than one, completely unrelated selector on a rule.

Cool article, but now I've

Cool article, but now I've got evil ideas in my head about hacking a templateing system into drupal_build_css_cache.

I didn't, but now I do.

I didn't, but now I do. Interesting...

Please note, this entry has been closed to new comments.