Why bother with a CSS build process?

Posted on by Brad

Simple web applications can be built quickly, and the original source code sent to users’ browsers for use. While this allows for rapid prototyping and quick turnaround, it’s not a workable model in the long run for a number of reasons:

  1. You are exposing your source code to anyone who may be interested
  2. Raw source code is generally larger than it needs to be (whitespace, comments, long variable names, etc)
  3. A larger project should be split into many source files, and if served directly to the end user separately, this results in a lot of overhead

For Javascript, we use the excellent Closure compiler from Google to combine and minify our code, as well as introduce a modicum of type safety and static code checking.

The build process for CSS often gets much less attention than that for Javascript, since CSS is generally much smaller and less frequently changed. In an application like Lucidchart, though, CSS can be a major part of the application’s download weight, and deserves careful thought.

LESS

The core of our CSS build process is the LESS compiler. LESS gives you the ability to include nested rules, variables, mixins, and even functions in stylesheet source code. Perhaps even more importantly, it provides a system for you to import one stylesheet into another, so that you can separate your styles into short, manageable files.

The LESS website gives great examples of its features and recommended usage. One thing we now do is name all of our LESS “libraries” (stylesheets that exist only to be included in other stylesheets) with a filename starting with an underscore. This is because we’re starting to use Play 2.0 in a number of projects (more on that in a later post), and Play’s build process will automatically compile any .less files that don’t start with an underscore into their own .css file.

Image inlining

One way to optimize a page’s loading time is to minimize the number of requests made to load the page. That means combining multiple stylesheets into one CSS file (which is what LESS is for). Using base64-encoded image URLs in stylesheets can also reduce the number of requests if you have a large number of images.

However, inlining all images can cause its own set of problems. Inlining very large images can dramatically slow down the download time of the CSS file. And if those large images don’t actually need to be loaded immediately (such as an image that’s only used in a dialog that’s seldom opened), it’s time and bandwidth spent for nothing.

The actual script we use to inline images is below. We check URLs that are based at the root as well as URLs that are relative to the location of the CSS files. If the image is less than 5096 bytes (an arbitrary limit that struck a good balance for us), we replace the URL with a base64-encoded data URI.

We also exit with an error code if an image URL is included that can’t be resolved to a file in our webroot. This is a kind of static code checking for CSS, to be sure we don’t have any broken links in our stylesheets.

#!/usr/bin/php -q
\n");

for($i = 1; $i < sizeof($argv); $i++) {
	$content = file_get_contents($argv[$i]);
	$original = $content;

	if(preg_match_all("_url\\([\"']?/([^)\"']+)[\"']?\\)_", $content, $matches)) {
		for($m = 0; $m < sizeof($matches[0]); $m++) {
			$full = $matches[0][$m];
			$uri = $matches[1][$m];
			$fileName = $_ENV["LUCID_HOME"].'/app/webroot/'.$uri;
			if(file_exists($fileName)) {
				$size = getimagesize($fileName);
				if($size !== false) {
					echo "Replacing $full\n";
					$fileContent = file_get_contents($fileName);
					if(strlen($fileContent) < 5096) {
						$replacement = 'url(data:'.$size['mime'].';base64,'.base64_encode($fileContent).')';
						$content = str_replace($full, $replacement, $content);
					}
					else {
						echo "Image $fileName too large\n";
					}
				}
				else {
					echo "Could not get image size.\n";
				}
			}
			else {
				echo "Could not replace $full ($uri => $fileName) in {$argv[$i]}\n";
				exit(1);
			}
		}
	}

	if(preg_match_all("_url\\([\"']?([^)\"']+)[\"']?\\)_", $content, $matches)) {
		for($m = 0; $m < sizeof($matches[0]); $m++) {
			$full = $matches[0][$m];
			$uri = $matches[1][$m];

			if(substr($uri,0,5) == 'data:' || substr($uri,0,1) == '/')
				continue;

			$pathinfo = pathinfo($argv[$i]);
			$fileName = $pathinfo['dirname'].'/'.$uri;
			if(file_exists($fileName)) {
				$size = getimagesize($fileName);
				if($size !== false) {
					echo "Replacing $full\n";
					$fileContent = file_get_contents($fileName);
					if(strlen($fileContent) < 5096) {
						$replacement = 'url(data:'.$size['mime'].';base64,'.base64_encode($fileContent).')';
						$content = str_replace($full, $replacement, $content);
					}
				}
				else {
					echo "Image $fileName too large\n";
				}
			}
			else {
				echo "Could not replace $full ($uri => $fileName) in {$argv[$i]}\n";
				exit(1);
			}
		}
	}

	if($original != $content)
		file_put_contents($argv[$i], $content);
}

Results

We currently have 129 LESS files in our core code repository. Large sections of code are reused across different areas of our application (editor, document list, team management page, etc), resulting in better code maintainability. The final download size is much better than hand-written CSS, and is all included in a single file. We also reduce the number of image downloads by several dozen while only modestly increasing the size of our CSS file.

[Note: Have we piqued your interest in our project? We're hiring!]

This entry was posted in Tech Talk. Bookmark the permalink.

5 Responses to Why bother with a CSS build process?

  1. anon says:

    CSS is style, not source code. Exposing it to “anyone who may be interested” is a non-issue.

    If your CSS is so massive that you have to shrink it down and glom it all together just to get reasonable speed, you’re doing something wrong.

    base64-encoding images into the CSS punishes the client in terms of bandwidth and CPU workload. If you have so many requests that you have to resort to hacks like sticking images into CSS, that’s a symptom of poor design. Heard of CSS sprites?

  2. Tyler says:

    We all use CodeKit for LESS/SASS compiling. My favorite feature the syntax checkers that run whenever you save. CSS is one of those ‘graceful’ languages that don’t pipe up loud enough when you have some faulty syntax…

  3. Paul says:

    129 files! Good luck with that.

    I’ve only worked on one LESS project with 2 files and maintainability was annoying to say the least. Inspecting the page to follow a comment to the line where the rule is in the LESS code, changing it and then verifying that it actually compiled as well as verifying that the browser or any other server level caching isn’t meddling with the results makes the whole process tedious. I find using to-spec CSS much less annoying and quicker. Not to mention that it makes the project easier to pass from shop to shop (don’t forget there are SASS shops out there too — CSS is a standard, preprocessors are not). I also find working in single files (where available) much less confusing when it comes to the cascade. No bracket counting and a scroll down the page should reveal cascade problems without troubleshooting compile order.

    I can see how preprocessors make building sites faster, but the build of a site is only a small part of the overall lifecycle of a site. Most site builds I’m involved in require features to be dropped from initial launch and be added in a “phase 1″ after the launch. That is never the last time a feature is added to a site, so simple unobstructed structures are what I stick with. If you only look at your own code and LESS/SASS work for you, more power to you.

    If combining CSS is the main reason you’re doing this, I believe that YUI compressor can compress multiple CSS files into a single output and if it can’t it’s quite easy to find a command line script to pipe files together before running it.

  4. Christoph Wagner says:

    Inspecting the page to follow a comment to the line where the rule is in the LESS code

    Chrome (experimental feature) and Firefox (via Firebug extensions) both support SASS debug info to directly find the proper scss/sass file.

    I’d imagine that there are similar tools for LESS.

  5. Ben Dilts says:

    Yep, 129 files! Remember, guys, this isn’t for a website. This is for our actual application, which is constructed entirely using HTML, CSS, and Javascript. 129 files for CSS isn’t that bad when you consider our Javascript codebase is about ten times that.

    I agree, this is way, way overkill for someone’s drupal page. Even for most simple web apps. But for us (and some subset of serious web developers out there), this fills a real need and helped cut our full-page cold-cache load times on our instant demo from 8 seconds average to about 4.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>