Less annoying CSS using PHP Preprocessing
I’ve always found working with large CSS files annoying, and a while back I set out to make it less so.
My 2 greatest CSS pet peeves are/were:
- You can’t use // for comments – Not that big a deal, just annoying.
- You can’t nest CSS rules within each other, making contextual selectors a real pain if used extensively.
As an example: What I’d like to write is the following:
#navbar { border: 1px solid black; //Applies to h2s within the navbar h2 { background-color: black; color: white; } li { float: left; width: 100px; } }
Note that by its context, I can tell that the li rule only applies when it is within the #navbar element.
But am stuck writing:
#navbar { border: 1px solid black; } /* Applies to h2s within the #navbar */ #navbar h2 { background-color: black; color: white; } #navbar li { float: left; width: 100px; }
So, what I’ve done is written the following PHP code to allow me to write my CSS code the way I’d like to.
And then transform it to compliant code by passing it through a PHP function called renderCSS.
I’m sure there are far more elegant and efficient ways of doing this, but at the time I was writing it, I just needed to get it working pronto.
So, without further ado…
/** * Renders a snippet of CSS * * @param string $cssText * @return string */ function renderCSS($cssText) { $data = preg_replace('/(//.*n)/',"",$cssText); // remove single line comments, like this, from // to \n $data = preg_replace('/(t|r|n)/',"",$data); // remove new lines \n, tabs and \r $data = preg_replace('/(/*[^*]**/)/','',$data); // remove multi-line comments /* */ $data = preg_replace('/(/*[^/]**/)/','',$data); // remove multi-line comments /* */ $data = preg_replace('/(s+)/', ' ',$data); // replace multi spaces with singles $data = fixNesting($data); $data = preg_replace('/[^}{]+{s?}/', '', $data); //Remove empty rules $data = preg_replace('/s*{s*/', "{", $data); $data = preg_replace('/s*}s*/', "}", $data); $data = preg_replace('/}/', "}n", $data); return $data; } // Helper callback function for fixNesting (below) function fixNesting_buildSelector($outer, $inner) { $outerPieces = split(",", $outer); if (trim($inner) == "IEHACK") { foreach ($outerPieces as $o) { $resultPieces[] = "* html " . $o; } } else { $innerPieces = split(",", $inner); $resultPieces = array(); foreach ($outerPieces as $o) { foreach ($innerPieces as $i) { $resultPieces[] = $o . " " . $i; } } } return join(",", $resultPieces); } // Helper callback function for fixNesting (below) function fixNesting_selector($selectors, $depth) { $newSelector = $selectors[0]; for($j = 1;$j<$depth; $j++) { $newSelector = fixNesting_buildSelector($newSelector, $selectors[$j]); } return $newSelector; } function fixNesting($cssText) { $result = array(); $pieces = split("{", $cssText); $selectorStackIndex = 0; $selectorStack = array(); $currentPieceIndex = 0; for ($i = 0; $i < count($pieces); $i++) { $piece = $pieces[$i]; while (($closeBracketPos = strpos($piece, "}")) !== false) { if ($closeBracketPos > 0) { $result[] = substr($piece, 0, $closeBracketPos); } $result[] = "}"; $selectorStackIndex--; if ($selectorStackIndex > 0 ) { $result[] = fixNesting_selector($selectorStack, $selectorStackIndex); $result[] = "{"; } $piece = substr($piece, $closeBracketPos+1); } if (trim($piece) == '') { continue; } // Inner Rule $endOfLastProperty = strrpos($piece, ";"); if ($endOfLastProperty !== false) { $result[] = substr($piece, 0, $endOfLastProperty+1); $piece = substr($piece, $endOfLastProperty+1); } if ($selectorStackIndex > 0) { $result[] = "}"; } // Whole piece is the selector $selector = $piece; $selectorStack[$selectorStackIndex++] = $selector; $result[] = fixNesting_selector($selectorStack, $selectorStackIndex); $result[] = '{'; } $result = join($result); return $result; }
Actually a really interesting concept. As long as you are doing preprocessing like this, it might be interesting to explore joining all css files into 1. For instance, certain pages might have their own specific css or page components might include css when in the page. It would be interesting to think of just letting your preprocessing class know about them and they would be joined into 1 request, thus reducing the number of total requests on page load. Cool post though.
This piece is awesome. It applies to both the exact same things that keep buggin me about css too. Hope the concept keeps getting pushed further.
@Zack I’ve implemented the changes you suggested and then some in the follow up to this post at: More Powerful CSS with Preprocessing. Thanks for the feedback.