I own and run a digital agency called Red Wolf Digital, for the most part my work involves building websites. We use an in-house content management system we call Mercury to build most of the sites we produce for clients. One of the things I’ve been trying to find for some time was a way to automatically minify the CSS used to style these sites.
There are loads of CSS and JavaScript minifiers out there, but I was struggling to find one that would handle @import
statements. I’m a great believer in object orientated coding principles and the benefits afforded to you by breaking large, monolithic entities into smaller more manageable chunks. Something that was kept in mind whilst designing and developing Mercury, which of course, makes full use of CSS @import
rules. The lack of a minifier that will handle this has prompted me to write a small script to do it for me, I include it here should anyone happen across it and get some use from it.
Firstly, to run through exactly what we’ll be doing. We will:
- grab the contents of the main controller CSS file
- remove the comments
- find all the imported CSS files
- loop through the imported files
- grabbing the contents
- removing the comments
- replacing the import statement with the file contents
- compress the extra whitespace into single spaces
- fix any relative URLs
- change the header content type and echo out the CSS
At this point it’s worth mentioning that there are many more things that could be done to compress the resulting CSS, such as removing the whitespace within the decelerations block. I’m not bothering to do so here, but a few extra regular expressions will likely allow you to do that.
/** * Function that takes a string containing CSS and removes all the comments, * leaving the CSS unmangled. * * Taken from the example at: http://stackoverflow.com/a/1581063 * * @param String $css Sting containing the CSS to be de-commentified * @return String The comment free CSS */ function removeComments( $css ) { $regex = array( "`^([\t\s]+)`ism"=>'', "`^\/\*(.+?)\*\/`ism"=>"", "`([\n\A;]+)\/\*(.+?)\*\/`ism"=>"$1", "`([\n\A;\s]+)//(.+?)[\n\r]`ism"=>"$1\n", "`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism"=>"\n" ); return preg_replace( array_keys( $regex ), $regex, $css ); } /** * Parses a string containing CSS and creates an array of all the import rules. * * @param String $css String containing the CSS to parse * @return Array Array containing the CSS matched, a regex to replace * the CSS and the filename of the imported CSS file * for each of the import matches in the given string. */ function getImports( $css ) { // Match all the import statements preg_match_all( "/@import url\( \"(.*)\" \);/", $css, $matches ); $includes = array(); foreach( $matches[0] as $key => $match ) { $import = "@import url\( \"{$matches[1][ $key ]}\" \);"; $includes[] = array( "css" => $match, "regex" => "/".str_replace( "/", "\/", $import )."/", "file" => $matches[1][ $key ] ); } return $includes; } /* GET THE ROOT DIRECTORY AND BASE CSS FILE FROM THE URL **********************/ $rootDirectory = $_GET["rootDirectory"]; $rootFile = $_GET["rootFile"]; /* GET THE FILE CONTENTS, REMOVE COMMENTS *************************************/ $css = file_get_contents( "{$rootDirectory}{$rootFile}" ); $css = removeComments( $css ); /* GET IMPORTED FILES, LOOP THROUGH THEM REPLACING WITH CONTENTS **************/ foreach( getImports( $css ) as $import ) { $fileName = "{$rootDirectory}{$import["file"]}"; // Add a @ in case the file doesn't exist and thows a warning $importedCSS = @file_get_contents( $fileName ); $importedCSS = removeComments( $importedCSS ); $css = preg_replace( $import["regex"], $importedCSS, $css ); } /* CONDENSE ANY EXTRA WHITESPACE INTO JUST A SINGLE SPACE CHARACTER ***********/ $css = preg_replace( "/\s+/", " ", $css ); // Fix any malformed relative URLs $css = preg_replace( "/(\.\.\/)+/", "../", $css ); header( "Content-Type: text/css" ); echo $css;
The above code is expanded for readability, there are a range of places you could drop some variables and nest function calls.
The two $_GET
parameters are used to pull in the relevant CSS file. rootDirectory is the directory where the CSS file lives and where all import rules are relative to. rootFile is the CSS file that you wish to compress.
We remove comments each time a file is pulled in, which is probably not the most efficient way to handle them, however we have large header comments in each of our source files that we’d rather have stripped out earlier – rather than have a massive bloat as we concatenate all the CSS files.
We correct for malformed relative URLs from the imported CSS files, changing them all to “../” as we serve the CSS from a css/ directory at the site root, whereas all our included files live in subdirectories of that. You should be able to change or remove this based on how you’re referencing resources in your CSS (or alternatively, write CSS that takes this into account).
Only one level of @import
statements are checked for, since that’s all we tend to use at Red Wolf Digital. It’s easy enough to set up a recursive function call to check each of the imported files’ contents as you pull them in. Likewise only @import: url( "[stylesheet]" );
formatted imports are catered for, feel free to extend upon it to add support for the various other styles accepted for importing stylesheets.
We are compressing and serving the CSS every time in this example, a better solution is of course to cache a pre-compressed version to disk so you can serve it to the browser if nothing has changed. You can set that up on your own!