Preload CSS/JavaScript without execution

Preloading components in advance is good for performance. There are several ways to do it. But even the cleanest solution (open up an iframe and go crazy there) comes at a price - the price of the iframe and the price of parsing and executing the preloaded CSS and JavaScript. There's also a relatively high risk of potential JavaScript errors if the script you preload assumes it's loaded in a page different than the one that preloads.

After a bit of trial and lot of error I think I came up with something that could work cross-browser:

  • in IE use new Image().src to preload all component types
  • in all other browsers use a dynamic <object> tag

Code and demo

Here's the final solution, below are some details.

In this example I assume the page prefetches after onload some components that will be needed by the next page. The components are a CSS, a JS and a PNG (sprite).

window.onload = function () {

    var i = 0,
        max = 0,
        o = null,

        // list of stuff to preload
        preload = [
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.png',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.js',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.css'
        ],
        isIE = navigator.appName.indexOf('Microsoft') === 0;

    for (i = 0, max = preload.length; i < max; i += 1) {

        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];

        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;

        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }

};

A demo is here:
http://phpied.com/files/object-prefetch/page1.php?id=1
In the demo the components are delayed with 1 second each and sent with Expries header. Feel free to increment the ID for a new test with uncached components.

Tested in FF3.6, O10, Safari 4, Chrome 5, IE 6,7,8.

Comments

  • new Image().src doesn't do the job in FF because it has a separate cache for images. Didn't seem to work in Safari either where CSS and JS were requested on the second page where they sould've been cached
  • the dynamic object element has to be outside the head in most browsers in order to fire off the downloads
  • dynamic object works also in IE7,8 with a few tweaks (commented out in the code above) but not in IE6. In a separate tests I've also found the object element to be expensive in IE in general.

That's about it. Below are some unsuccessful attempts I tried which failed for various reasons in different browsers.

Other unsuccessful attempts

1.
I was actually inspired by this post by Ben Cherry where he loads CSS and JS in a print stylesheet. Clever hack, unfortunately didn't work in Chrome which caches the JS but doesn't execute it on the next page.

2.
One of the comments on Ben's post suggested (Philip and Dejan said the same) using invalid type attribute to prevent execution, e.g. text/cache.

var s = document.createElement('script');
s.src = preload[1];
s.type = "text/cache";
document.getElementsByTagName('head')[0].appendChild(s);

That worked for the most parts but not in FF3.6 where the JavaScript was never requested.

3.
A dynamic link prefetch didn't do anything, not even in FF which is probably the only browser that supports this.

for (i = 0, max = preload.length; i < max; i += 1) {
    var link = document.createElement('link');
    link.href = preload[i];
    link.rel = "prefetch";
    document.getElementsByTagName('head')[0].appendChild(link);
}

Then it took a bit of trial/error to make IE7,8 work with an object tag, before I stumbled into IE6 and gave up in favor of image src.

In conclusion

I believe this is a solution I could be comfortable with, although it involves user agent sniffing. It certainly looks less hacky than loading JS as CSS anyways. And object elements are meant to load any type of component so no semantic conflict here I don't believe. Feel free to test and report any edge cases or browser/OS combos. (JS errors in IE on the second page are ok, because I'm using console.log in the preloaded javascript)

Thanks for reading!

This entry was posted on Wednesday, April 21st, 2010 and is filed under CSS, JavaScript, images, performance. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.


Get notification for future posts: follow me on Twitter or subscribe to my RSS feed

Somewhat related posts

27 Responses to “Preload CSS/JavaScript without execution”

  1. qFox Says:

    Have you tried exchanging one with the other? So, load javascript as stylesheet and stylesheet as javascript? Wouldn’t be surprised if that was a cheap alternative. If you can stand the reported errors, that is. Haven’t tested this, just a thought.

  2. Stoyan Says:

    Well, JS as print stylesheet is where I started from, didn’t work properly in Chrome.

    CSS as JS will be a syntax error most likely.

  3. Ben Cherry Says:

    Awesome! I’ve been meaning to follow up on my original research and come up with a solid solution, but this looks like just that :)

    qFox - Yeah, CSS as JS will through a syntax error, and JS as CSS is dangerous in Chrome (actually, both are). Chrome forces sanity onto the type when it goes into the cache, so even though your JS is loaded as a stylesheet, Chrome ignores the “(application|text)/javascript” type and gives it “text/css” instead. Then, on future pages, requests for this file will be served from cache with “text/css”, and Chrome will refuse to execute anything with that type as JavaScript.

  4. Luke Smith Says:

    This would make a great YUI 3 Gallery module. Using this plus something like Nicholas’s Idle Timer to preload resources for yet-to-be-displayed pages or dynamic content sections would be a great fit.

  5. stoimen Says:

    Great post! This approach seems to be working under all browsers which is strange :) and awesome!

  6. Ionut Popa Says:

    Great research, are you using this in production mode?

  7. Arnout Says:

    The downside of this technique is that the Object tags add “spacing” at the bottom of your page.

  8. Kyle Simpson Says:

    The “text/cache” invalid mime-type on a script tag does not work in FF or Opera, but *does* work in IE, Chrome, Safari, and others. By that, I mean, the element *does* get fetched into cache for later use (on same page or subsequent pages).

    And contrary to some observations above, in Chrome, the script pulled in this way is not tagged with an invalid type that prevents it from being used from cache as a valid script — subsequently adding a regular script tag with a valid mime-type on it causes the script to be pulled from cache and executed immediately. This is the trick that LABjs currently uses for those browsers, and thus far the testing proves it to be reliable.

    I’m interested in the “object” trick, that’s interesting, and may be a valid approach for CSS preloading. I wonder if the “onload” works reliably. I have the Image.src approach, seems so much more hacky than other tricks. But I can see that it might be a valid solution. Again, is onload reliable?

  9. Ben Cherry Says:

    Kyle - You’re correct, the “text/cache” script doesn’t cause the invalid type in Chrome, it’s loading a script in a link tag with rel=”stylesheet” that does (my original attempt).

  10. Jason Persampieri Says:

    Just for grins, how horrible is it to load the file as a string, then append it to a new script tag or eval it when ready?

  11. Weston Ruter Says:

    A better IE detection function: http://gist.github.com/358029

  12. Aaron Peters Says:

    I ran your test page through IE7 on Vista (empty cache) twice, and got this:
    - first page: 3 files load as expected
    - second page: the JS and CSS file are downloaded again (200 response), but not the PNG

    How’s that for weirdness?

  13. Mathias Bynens Says:

    Awesome research. Thanks, Stoyan!

    I took the liberty of optimizing the for loop into a more efficient reverse while loop and rewriting your code as a function that takes an array of files as argument. See gist 375496.

    Example:

    preload([
    'http://tools.w3clubs.com/pagr2/1.sleep.expires.png',
    'http://tools.w3clubs.com/pagr2/1.sleep.expires.js',
    'http://tools.w3clubs.com/pagr2/1.sleep.expires.css'
    ]);

  14. Philip Tellis Says:

    Nice. Been waiting for this post for a long time man. Good to see it done.

  15. FrankyBoy Says:

    Normal people use else instead of continue ;)

  16. Aaron Barker Says:

    This is awesome. I was just searching for a solution to the preloading thing and couldn’t find anything so started making my own. Naturally this comes along right after I finished mine and is much more elegant.

    Great job!

  17. Aaron Barker Says:

    So I just tried replacing my solution with yours and found a detail that may be helpful to others. I am wanting to have an optional loading indicator so I can have my loading script run silently on a login screen, but then have a dedicated loading page (like gmail)… both using the same script.

    In trying to use your object method I found that the load event doesn’t work on OBJECT elements like it does on IMG and IFRAME elements (which I was using before). So I am not able to know when the files are actually done loading to update the progress bar and to redirect off of the loading screen.

    Just a little tidbit for others if they are wanting to do likewise, and a call for help if anyone has any suggestions.

  18. Stoyan Says:

    @Aaron, I tried IE8 in IE7 mode on Vista and it all worked fine… In my experience so far IE8-in-IE7-mode/Vista mode behaves just like real IE7/Vista. But this could be an exception.

  19. Serj Says:

    So in order to use this, do I need to rename my .js and .css to the above or can I just
    preload = [
    'http://url .com/style.css',
    'http://url .com/jquery-plugins-adds.min.js',
    ],

  20. Stoyan Says:

    Serj, what you suggest should work

  21. John Says:

    Why shouldnt we just use xmlhttprequest to prefetch ?

  22. Stoyan Says:

    @John, sure, but there’s the same domain restriction

  23. Aaron Peters Says:

    How about preloading other objects, like font files (.eot, .ttf)?

    Hmm … as I type this I’m thinking: why ask you to do the testing and not simply do it myself? Yes, I will. Will post results here.

  24. Aaron Peters Says:

    OK, I created a test page for preloading font files: .eot, .ttf, .svg, .woff.
    It does not work, except for in IE and Opera 10.

    Results:
    - Chrome: FAIL. Chrome shows the yellow bar at top of the page asking if I want to download files + immediately a ‘Save file’ window appears for the .eot file (first in the array).
    - IE7 and IE8: PASS. All 4 files are download after onload and no feedback to the user.
    - FF3.0: FAIL: the 4 files are downloaded but the yellow bar appears at top of page with the message “Additional plugins are required to display all the media on the page”.
    - Opera 10.61: PASS. All 4 files are download after onload and no feedback to the user.

    The test page is here: http://www.aaronpeters.nl/sandbox/wpo/preload-font-files.html

    Stoyan, any thoughts on why it fails in Chrome and FF and a solution?

    Use case: blog uses @font-face with a cool font: preload the font files when user visits the non-blog webpages.

  25. Aaron Peters Says:

    Follow up on the preloading of font files…

    I did some more testing.
    As it turns out, using our code to preload the .svg file works fine in all browsers, and fails in Chrome/FF for the other 3 files. The difference is in the mime type and gzip on/off, but I’m not sure this has anything to do with it.
    .eot = text/font, gzip on
    .ttf = text/font, gzip on
    .woff = text/plain, gzip off
    .svg = image/svg+xml, gzip on

    Next, I tested preloading the 3 failing font files with new Image().src in *all* browsers (not just in IE). Guess what? It works in *all* browsers: files are downloaded, user gets no feedback on the downloading, no warnings, errors or yellow bars.

    Of course, you really don’t want to preload all 4 files for all browsers since browsers don’t support all font files (and really only need 1):

    .eot for IE4+
    .ttf for Firefox 3.5+ , Opera 10+, Safari 3.1+, Chrome 4.0.249.4+
    .svg for iPad and iPhone
    .woff for Firefox 3.6+, Internet Explorer 9+, Chrome 5+

    Source: http://www.fontsquirrel.com/fontface

    Stoyan, can you ‘upgrade’ your code with some nifty browser detection to enable preloading the right font file(s) in the right browser? I hope so ;-)

  26. Aaron Peters Says:

    Final comments about preloading font files (nutshell: no go in IE and FF)

    1) Je suis un dumbass. In the previous comments you can read I tested with Firefox 3.0 which makes no sense at all since that browser does not support @font-face :(
    New tests were done with FF 3.5.4

    2) The preloading does NOT work on IE7, IE8 and FF3.5.4 (yes, conclusions earlier were wrong).
    IE and FF actually download the font files without any issues (test page: http://www.aaronpeters.nl/sandbox/wpo/preload-font-files-3.html) but the font file is not used from cache on the next page (
    http://www.aaronpeters.nl/sandbox/wpo/preload-font-files-validate.html). The browser does an unconditional request and downloads the file from server.

    Chrome5 and Opera10.6 are sweet as they do what I want: preload nicely and use font file from cache.
    But since these browsers are the fast ones, the benefit is small and not worth the hassle.

    Behavior on iPhone/iPad: don’t know, didn’t test it.

    Sorry Stoyan and readers, next time I’ll try not to make mistakes and not post 4 comments but only 1 or 2…

  27. sajal kayan Says:

    Hi,

    I tried this for preloading a swf file (which may be needed for next page in the flow) on firefox with clean cache.

    I think since this is an object tag, the flash is being executed.

    the flash file tries to load some ‘config.xml’ thingie in the wrong manner.

    Is there a better way to preload .swf files?

    -Sajal

Leave a Reply