Data URIs and automation with CSSEmbed and Ant
(Originally posted October 2010)
When used in conjunction with CSS, data URIs provide an interesting approach to improving web performance and also helping with maintainability. Using data URIs, images can be directly embedded within CSS files as strings of text. This has two key benefits:
- Less HTTP requests made by web pages, particularly those with rich designs requiring many images, meaning faster loading web sites.
- No need anymore for complex CSS sprites to reduce HTTP requests. Increasing maintainability and productivity.
Great, but working with data URIs in development code just isn’t practical. Its a maintainability nightmare worse than the most complex of CSS sprites. The key to making use of data URIs in CSS feasible is automation; automatically converting images references to data URIs as part of the production build process.
Nicholas C. Zakas’ excellent CSSEmbed is a tool which automatically replaces all image file references in a CSS file with data URIs. This tool is the basis of which this post builds upon.
Automating with Ant
CSSEmbed is a command line tool. In order to easier integrate it with Apache Ant build files I have written an Ant task to accompany CSSEmbed. Command line programs can run directly in Ant, but a specific task makes things much tidier and more readable. Here’s a complete build file showing the task in action:
The first important step is to define the Ant task so Ant knows about it.
Now the CSSEmbed task is ready to run. The task is an implicit file set, its child tags must specify what files to include or exclude for embedding. The fromdir is the root directory tree of the file set. The destdir attribute specifies where to output the embedded files. Each CSS file in the file set is separately converted. The suffix attribute specifies what suffix to add to output files, i.e. site.css becomes site-datauri.css.The task’s other attributes pretty much match CSSEmbed’s input parameters.
In this example, the task runs twice creatinge two separate output files. The first file is the standard data URI file. Most browsers support data URIs and can use this. The second output file uses the mhtml option to create a CSS file to be used just by earlier versions of Internet Explorer. While Internet Explorer 6 and 7 don’t support data URIs, they do support MHTML(MIME HTML). MHTML allows data embedded within a resource file to be referenced. In this instance it is particularly useful as you can embed base 64 image data in the same CSS file. MHTML is a quite ugly and easy to break with minor issues such as missing carriage returns. Fortunately CSS Embed takes care of this for you. You never even have to look at the code.
Also notice the mhtmlRoot option. When specifying the location of the data to embed in MHTML, the full absolute url must be specified. This options only changes the data in output CSS file. It doesn’t effect output file names or directory locations.
Browsers and progressive enhancement
So now there are three versions of the CSS file
- site.css The original CSS file,
- site-datauri.css The CSS file with data URIs embedded,
- site-mhtml.css The Internet Explorer 6 and 7 version of the file.
Now, we need to choose which CSS file to send to the browser.
The simplest way is to use IE conditional comments. Normally I’d shy away from this technique which is essentially a form of browser sniffing, but data URIs are so well supported in browsers other than IE < 8 I’m willing to make an exception.
You might notice that the conditional comment for the MHTML versionincludes IE8, despite me saying the MHTML version was just for IE 6 and 7. This isn’t a mistake. IE8 supports both data URIs and MHTML. However, IE8 only allows data URI strings less than 32Kb, but does support data URIs embedded via MHTML which are larger than 32Kb. So I’d recommended choosing the MHTML option for IE8 as its there.
Also note the use of the NOT IE conditional comment, unlike some approaches out there, its important not to send the data URI version of the file to the browser and then the MHTML version. This is important. Although the data URI styles will be overwritten with the MHTML versions the damage has already been done. The browser has wasted time unneceesarily downloading the data URI version of the file. This defeats the whole performance optimsation point of using data URIs, downloading effectively the same file twice. This same logic also applies to using browser hacks such as *background in the CSS file to provide the MHTML option in the same file, potentially almost doubling the size of the CSS.
If you’re concerned about any browsers out there which aren’t IE < 8 and don’t support data URIs, take a look at the following progressive enhancement technique using Javascript (adapted from here).
The Javascript should be placed in the head. It attempts to load a very simple image using a data URI. If there’s an error or the image seems to load, but its the wrong dimensions, it can be assumed data URIs aren’t supported. If Javascript isn’t enabled, play it safe and use the fallback CSS. The fallback approach is to use the the MHTML stylesheet. A more cautious approach would be to fallback to the original, non data embedded, stylesheet. The feature detection approach also means the IE8 must use data URIs and not MHTML, meaning it is limited to 32Kb data URI strings.
Caching caveats
Normally on a typical web site or application I’d recommend combining and minifying all the CSS into one file; then using a ‘never expires’ HTTP caching technique together with URL fingerprinting to ensure the browser only redownloads the file if it changes (or if the cache is cleared). The client takes one hit downloading a 30k CSS file and then that’s it.
However, applying this technique to data URI embedded CSS files might not be so clever, particularly if there is a large accumulative file size of images embedded within the CSS file. It could mean the browser redownloading not only all your site’s CSS, but all of your sites images in one hit every time you make fix a small CSS bug or tweak one image.
Therefore, if using the approaches discussed in this post, think carefully about how you manage CSS files to optimise performance.
You can download the CSSEmbed Ant task from my fork on GitHub