adequately good

decent programming advice

written by ben cherry




Preloading JS and CSS as Print Stylesheets

UPDATE: This technique has turned out to be dangerous in Chrome. It seems that Chrome will load the JS files into the cache, but then set an implied type="text/css" on them. This means that it will refuse to re-use them as JavaScript on future pages, until they have left the cache. I can no longer recommend this technique, but hope that it was at least interesting. I'll be working on a follow-up post about alternatives.

One of Yahoo's Best Practices for Speeding Up Your Web Site is "Preload Components". Most people are already familiar with doing this for images. Code for that usually looks like this:

var img = Image();
img.src = "/path/to/something.jpg";

This code works in all browsers, and causes that image to be downloaded and placed in the browser's cache.

But what if we want to go further? For a lot of today's big applications, images are no longer the only bandwidth hog. An application I work on has more than 2MB worth of JavaScript, CSS, and data (stored in JavaScript files). We found that users on slow connections could take up to 60 seconds to load our application for the first time! Obviously, we could benefit from intelligently pre-loading this data in the background, on an earlier page where they don't need it yet. But how?

My first attempt was just to use the Image() method above, but point at our text files instead. This didn't work in Firefox. I'm not sure why, but those files never appeared in the cache with that method. So our next idea was to use <iframe> tags. This worked great, until we tried it in Internet Explorer. IE chose to warn the user that our page was trying to download data they didn't ask for, and blocked the downloads until they approved them. Obviously, this wouldn't work either.

This left us scratching our heads, but we eventually had a breakthrough. There is one other tag that is used to include text files in a web page. That's the <link rel="stylesheet"> tag. But this couldn't possibly work with JavaScript, could it? It turns out it could!

var preload = function (file) {
	var elem,
		tag = "link",
		attr = "href",
		extra = " rel=\"stylesheet\" media=\"print\" ",
		target = "head";

	elem = jQuery(["<", tag, extra, attr, "=\"", file, "\"<>/", tag, ">"].join(''));
	elem.load(function () {
		elem.remove();
	});
	jQuery(target).append(elem);

	return elem;
};

This function creates a temporary <link> tag in the <head> of your page, pointing at the requested file. To work across all browsers, we needed to give it the rel="stylesheet" attribute. To not cause preloaded CSS files to try and apply themselves to the current page, or trigger a re-flow, we added media="print". Once the element has loaded, it removes itself from the DOM, but the file remains in the browser's cache. While this example relies on the jQuery library, there is no reason it could not be easily re-written without such a dependency, or written as a jQuery plugin (providing jQuery.preload()).

This technique cut our aforementioned page load from 60s to 20s, over a slow connection. I think this is a complete solution, and seems to work in all major browsers. What do you think?

filed under javascript and performance