css_dryer: DRY Up Your CSS
Cascading style sheets (CSS) are wonderful but repetitive. Rails strongly discourages repetition, the don't repeat yourself (DRY) principle, so writing CSS feels ungainly in Rails.
So I am pleased to give you css_dryer, a plugin that converts DRY stylesheets into CSS.
You can always find the most up to date information in the README.
Nested Declarations
John Nunemaker brought this into the open recently over at RailsTips.org. He gave the following CSS declarations as an example of annoying repetition:
div#content { /* some styles for div#content ... */ }
div#content h2 { /* some styles for div#content h2 ... */ }
div#content a { /* some styles for div#content a ... */ }
And then these as his dreamy alternative:
div#content {
/* some styles for div#content ... */
h2 { /* some styles for div#content h2 ... */ }
a { /* some styles for div#content a ... */ }
}
(Note to self: get syntax highlighting going on this blog.) Done!
I thought this was a terrific idea so I sat down and wrote the code to do it, using John's example as a test case. It took a little longer than I thought it would, mainly due to the recursion. (I always fumble around with recursion until suddenly it falls into place — at which point it is so obvious that I wonder why I was being so thick.)
So you can now nest declarations as deep you like. The inline/multiline nature of each declaration will be preserved too. For example, the DRY stylesheet:
div {
/* comment for div */
color: green;
p {
/* comment for div p */
color: red;
b { color: blue;
}
}
Becomes this CSS:
div {
/* comment for div */
color: green;
}
div p {
/* comment for div p */
color: red;
}
div p b { color: blue; }
Any selector can be nested: descendant, child, adjacent, class, pseudo-class, attribute and id selectors are all handled.
Note also that @media blocks are preserved. So this, for example, is left untouched:
@media screen, projection {
div {font-size:100%;}
}
Nice.
Please be aware that nested, multiple, comma separated selectors are not handled correctly. Comma separated selectors, nested or otherwise, are now supported.
Variables
Another source of repetition is the lack of variables in CSS, leading to lots of search-and-replace shenanigans whenever you decide to change a value.
For example, websites often use the same colour in more than one place. If, say, the sidebar's border is the same colour as the footer's, CSS requires you to write:
#sidebar { border: 1px solid #fefefe; }
#footer { footer: 1px dashed #fefefe; }
Later on you might receive the traditional feedback on months of development work: "Make the border lighter." So now you have to hunt through your CSS looking for all occurrences of #fefefe and replace them with a lighter shade. Of course any decent text editor makes this trivial, but it's still annoying.
A variable would solve this neatly:
<% sleek_grey = '#fefefe' %>
#sidebar { border: 1px solid <%= sleek_grey %> }
#footer { footer: 1px dashed <%= sleek_grey %> }
Enter CssDryer
I took the code I wrote to convert DRY stylesheets into CSS à la Nunemaker, added in an ERB pre-processing step and structured it as a new templating system (c.f. rhtml). I then turned it into a plugin: css_dryer.
Unfortunately I haven't yet set up a public Subversion repository... Thanks to terrific help from the pukka chaps at Rails Machine, you can install the plugin from my shiny, new public Subversion repository:
$ script/plugin install http://opensource.airbladesoftware.com/trunk/plugins/css_dryer
So, how does it all fit together?
First, generate a controller to serve up your DRY stylesheets:
$ script/generate controller stylesheets
Edit the controller to look like this (thanks John and Topfunky!):
class StylesheetsController < ApplicationController
before_filter :set_headers
after_filter { |c| c.cache_page }
session :off
layout nil
private
def set_headers
headers['Content-Type'] = 'text/css; charset=utf-8'
end
end
Add this 1.2-style route to config/routes.rb:
map.connect 'stylesheets/:action.:format', :controller => 'stylesheets'
You can then put your stylesheets in app/views/stylesheets/. They will be cached in public/stylesheets/.
The stylesheets themselves should have an ncss extension, e.g. site.ncss.
You can reference the stylesheet in your views like this:
<link href='/stylesheets/site.css' rel='Stylesheet' type='text/css'>
or with the stylesheet_link_tag helper:
<%= stylesheet_link_tag 'site' %>
To Do
Here's a simple list:
Handle all selector typesNested, multiple, comma separated selectorsCaching- Instantiate the controller and route automatically in
init.rbso they don't have to be created by hand Make a stylesheet helper tag to use the DRY stylesheets
Anything else? Let me know what you think!

71 Comments
Jump to comment form