[ /js/restyle ]

In my last article about CSS, I wrote a script that would convert a valid CSS3 file into a JSON object.

This time around, I'm going to write some example functions detailing how you could remove unnecessary elements from the resulting object, transform it back into valid CSS.

Structure

The object has a fairly simple heirarchy:

cssObj (an object)
  main (an object)
    selectors (objects)
      attributes (key-> value pairs)
  mq (an array of mediaquery objects)
    expr (a string)
    rules (an object)
      selectors (objects)
        attributes (key->value pairs)

Since the internals of mq (the object's media queries) closely resembles the structure of the main object's, we can write a few functions and reuse a lot of code.

Reading the file

We need to start somewhere:

var fs=require("fs");

var fn=process.argv[2];

if(!fn || !fn.match(/^.*\.json$/){  // if the user didn't pass a valid filename
  console.log("try providing the name of a css.json file"); // complain
  process.exit(0); // and quit
}

// read from file
var css=JSON.parse(fs.readFileSync(fn,'utf8'));

So now we have our object. Let's grab a list of all the selectors used outside of our media query.

var selectors=Object.keys(css.main)
console.log(selectors);

That returns something that looks like: [ 'body ', 'a', 'a:visited', 'a:hover', 'pre ', 'code', ... ]

For my site, I started with purecss and started making changes. I only use a really small subset of what comes with pure, so I'm going to use the array.filter method to generate a list of the selectors I actually want to keep.

As you should know if you've been following along, I generate my content using markdown. I use some of the fancier classes from purecss for my template, but that's it. Each page consists primarily of:

[ 'h1','h2','h3','h4','h5','h6'
  ,'strong','em','del'
  ,'ul','ol','li'
  ,'p','pre','code','blockquote'
  ,'a']

...as well as whatever html I write inline, and whatever is in my template:

['html'
  ,'head','meta','link','title'
  ,'body','div','span','script'
  ,'i','b']

Half of those attributes aren't ever rendered, but I included them for completeness.

Quick and dirty filtering

I haven't forgotten about forms, buttons, and tables, but I hardly ever use them. In any case, now that I've written these scripts, it should be easy to extract them from some other css if I ever want to reinclude them.

var not=function(key){ // just another battle
  return function(k){ // in the never ending war
    return !k.match(new RegExp(key)); // against boilerplate
  }; // lest we forget
};
var keepers=selectors
  .filter(not('button')) // get rid of everything button related
  .filter(not('table')) // same with tables
  .filter(not('form')) // this gets rid of most of pure's junk
  .filter(not('pure-img')) // but let's not forget these..
  .filter(not('legend')) // I've never once used a legend
  .filter(not('\\[hidden\\]'))
  .filter(not('fieldset')) // nor a fieldset
  .filter(not('dfn'))
  .filter(not('abbr'))
  .filter(not('input'))
// console.log(keepers); // uncomment to see the results

That gives me an array of all the selectors I would like to keep. I can then use filter to pick out subsets of those selectors, and do things with them.

Reconstituting Rules

All of this business of trimming out parts that I don't want is a waste of time if I can't convert the resulting JSON back into valid CSS3, so let's do that.

var printRule=function(obj,sel){
  var rules=obj[sel];
  var res=[sel+" {"];
  Object.keys(rules).map(function(r){
    res.push("  "+r+":"+rules[r]+";");
  });
  res.push("}");
  console.log(res.join("\n"));
};

printRule(css.main,keepers[6]);

That prints out some of the the css for my table of contents.

.toc {
  clear:left;
  margin:3%;
  width:200px;
  border:3px dotted #AAA;
  float:right;
  display:hidden;
}

Nice! What if I wanted everything related to my table of contents?

keepers
  .filter(function(k){
    return k.match(/.toc/);
  })
  .map(function(k){
    console.log();
    printRule(css.main,k);
  });

That prints out the following:

.toc {
  clear:left;
  margin:3%;
  width:200px;
  border:3px dotted #AAA;
  float:right;
  display:hidden;
}

.toc > ul {
  padding-left:5%;
}

.toc > ul > li {
  list-style:none;
  padding-left:0px;
  padding:5px 0px 5px 0px;
}

Giving it a try

I have yet to try plugging the resulting CSS into my page, so for all I know, things are getting subtly mangled.

I can just pull out that last filter expression and print the results to a new CSS file to see if the page works well.

...aaand sure enough, my menu bars were all messed up. Most of the page styles look correct, but the menus are all wrong.

The only thing I can think of that I've left out is the media query, so I'll add that back in and see what I get.

After some investigation...

It turns out my JSON object wasn't quite valid! That means I have to go back and fix my first script's output. Since I'm going back anyway, I'm going to take the opportunity to clean everything up and make a nice module out of it!

I started by going through and ripping out all of the console.log statements. Then I wrapped up all the loose code into functions. Then I created an object mo (short for makeover). Then I factored a bunch of stuff and exported mo for use by other scripts.

It still needs some work, but I've posted the module here.

var mo=require("./makeover.js"); // load the module

var input=mo.toJSON("./pure.dark.css"); // load your css

/* Make changes here */

console.log(mo.toCSS(input)); // output css

So all I have to do is write some functions to delete or modify the parts of the CSS I'm not happy with, and call this script like so:

node modify.js > newCSS.css

Some example functions

Let's quickly delete all the selectors we don't want.

var S=Object.keys(input);

var useless=new RegExp([
'button','table','form','pure-img','legend','fieldset','dfn','abbr','input'
].join("|"));

S.map(function(s){
  if(s.match(useless))
    delete input.main[s];
  });

This approach is far sleeker than the filtering method above.

One last hack

As I'm pulling all the crap out my CSS, I've been careful not to get rid of things that I might actually need. As such, I've learned a lot about what it all means in the process.

I came across this pattern I didn't recognize:

.pure-u-1-24{
  width:4.1667%;
  *width:4.1357%
}

I was curious about what the star meant, and was surprised to find that it was an artifact introduced to the CSS landscape by IE7.

Since I'm not interested in supporting ancient browsers, I decided to purge it from my base CSS, rather than continually filter it from my processed CSS. I opened up my main css file, and used vim's search and destroy command:

:g/\*width/d

GONE.

Update

After putting some more work into these scripts, I've made them available on github.

A few custom lines of Javascript took my CSS down from 578 lines of code (12.172 kb) to 440 lines of code (7.87 kb).