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().srcto 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().srcdoesn'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
objectelement has to be outside theheadin most browsers in order to fire off the downloads - dynamic
objectworks 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, images, JavaScript, 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

April 22nd, 2010 at 12:44 am
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.
April 22nd, 2010 at 12:59 am
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.
April 22nd, 2010 at 1:40 am
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.
April 22nd, 2010 at 3:13 am
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.
April 22nd, 2010 at 3:37 am
Great post! This approach seems to be working under all browsers which is strange
and awesome!
April 22nd, 2010 at 4:58 am
Great research, are you using this in production mode?
April 22nd, 2010 at 6:06 am
The downside of this technique is that the Object tags add “spacing” at the bottom of your page.
April 22nd, 2010 at 8:02 am
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?
April 22nd, 2010 at 10:12 am
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).
April 22nd, 2010 at 10:54 am
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?
April 22nd, 2010 at 12:11 pm
A better IE detection function: http://gist.github.com/358029
April 22nd, 2010 at 1:56 pm
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?
April 22nd, 2010 at 2:06 pm
Awesome research. Thanks, Stoyan!
I took the liberty of optimizing the
forloop into a more efficient reversewhileloop 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'
]);
April 22nd, 2010 at 11:39 pm
Nice. Been waiting for this post for a long time man. Good to see it done.
April 28th, 2010 at 12:57 pm
Normal people use else instead of continue
April 30th, 2010 at 2:35 pm
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!
May 3rd, 2010 at 10:41 am
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.
May 12th, 2010 at 7:16 pm
@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.
May 29th, 2010 at 1:13 pm
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',
],
May 30th, 2010 at 5:11 pm
Serj, what you suggest should work
June 16th, 2010 at 1:33 am
Why shouldnt we just use xmlhttprequest to prefetch ?
June 18th, 2010 at 2:27 am
@John, sure, but there’s the same domain restriction
August 13th, 2010 at 4:24 am
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.
August 13th, 2010 at 4:58 am
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.
August 13th, 2010 at 6:53 am
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
August 17th, 2010 at 7:44 am
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…
August 25th, 2010 at 6:04 am
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
December 6th, 2010 at 1:18 pm
Very interesting work. I wonder if you might be better off using conditional compilation for the IE-specific stuff? That’s got to be better than checking the user agent string, right?
December 7th, 2010 at 12:23 am
[...] technique uses iframes so requires changing your existing scripts. Stoyan has gone on to explore using image and object tags to download scripts without the browser executing them, and then doing the parse-execute when the code is [...]
December 7th, 2010 at 3:14 am
Preload von JS ohne es auszuführen…
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…
December 14th, 2010 at 8:05 am
[...] 参考:http://www.phpied.com/preload-cssjavascript-without-execution/ headjs 源码 2010 12 14 Javascript Comments(0) « 得到某月有多少天 [...]
December 15th, 2010 at 1:48 am
Really interesting stuff you have here. It kept me reading. I understand the IE idea better now.
December 16th, 2010 at 1:46 am
[...] Stefanov, a former teammate and awesome performance wrangler, recently blogged about preloading JavaScript without execution. His approach is to download the script as either an IMAGE or an OBJECT element (depending on the [...]
December 20th, 2010 at 3:52 am
HI Stoyan
why not use iframe instead of object? In some test I made , the two element looks exact the same thing. Thank you~
December 21st, 2010 at 8:02 am
[...] control over how and when JavaScript files are loaded and executed on a page. It does so by using Stoyan Stefanov’s approach of preloading JavaScript without executing it and has the pleasant side effect of enabling parallel [...]
December 22nd, 2010 at 5:12 am
YUI 3 module in Gallery using this technique. http://yuilibrary.com/gallery/show/preload
February 4th, 2011 at 6:05 am
[...] technique uses iframes so requires changing your existing scripts. Stoyan has gone on to explore using image and object tags to download scripts without the browser executing them, and then doing the parse-execute when the code is [...]
March 7th, 2011 at 7:56 am
[...] Stefanov explains how to preload CSS and JavaScript without executing it – and no, he doesn’t use [...]
March 17th, 2011 at 4:43 pm
[...] Stefanov describes a better workaround on his blog. He advocates including some client-side Javascript that’ll include the resources in a [...]
March 17th, 2011 at 5:03 pm
I think that your solution is a great approach most of the time, but fails in some cases. I extended your test a bit at http://blog.redfin.com/devblog/files/2011/03/page_one_a.html, and wrote up a possible alternative at http://blog.redfin.com/devblog/2011/03/prefetching_web_content_trials_and_tribulations.html
March 31st, 2011 at 3:50 am
[...] 此发现引导我提出了将JavaScript拆分的建议:一部分初始加载页面用于渲染页面的代码;另一部分是可以稍后加载的用于DHTML和Ajax的代码(参见此文或是《高性能网站建设进阶指南》的第三章)。网站通常在window的onload事件处理函数中下载页面稍后需要的代码。Gmail Mobile team 发现 当后续代码到达浏览器时UI会被锁住,这是不恰当的。总之,这些用于DHTML/Ajax的代码甚至很可能不会被用到。他们是我发现的第一批找到方法脚本 分离下载阶段和脚本加载的解析-执行阶段的家伙们。他们使用的方法是对所有代码加上注释,然后当需要时再移除注释分隔符并且执行它们。GMail的技术使 用了iframe,所以需要改变你现存的代码。Stoyan已经开始探索使用image和object标签加载脚本以避免执行他们,只有到需要执行时再进行专门的解析和执行。 [...]
April 13th, 2011 at 7:39 am
[...] 对于不在当前页面中的链接,如果需要预下载后续内容可以用js来实现,请参考这篇文章Preload CSS/JavaScript without execution [...]
April 18th, 2011 at 4:39 am
[...] 对于不在当前页面中的链接,如果需要预下载后续内容可以用js来实现,请参考这篇文章Preload CSS/JavaScript without execution [...]
April 28th, 2011 at 6:04 pm
[...] סטפנוב מציע להשתמש בתג object ולטעון לתוכו את הכתובות. בשביל אקספלורר אפשר פשוט לכתוב new Image().src=url, גם עבור קבצי סגנון [...]
June 10th, 2011 at 11:56 pm
FWIW, this script seems to work in IE9 as well.
June 22nd, 2011 at 7:36 am
[...] 2009, Google’s article* http://www.phpied.com/preload-cssjavascript-without-execution/ © 2011 Unknown [...]
June 24th, 2011 at 2:48 pm
[...] For the current page is not the link, if need to download the following content can use js to achieve, please refer to this articlePreload CSS/JavaScript without execution [...]
July 8th, 2011 at 10:25 pm
You can cache swf files on at least IE and Chrome using the following:
The “declare” attribute tells the browser to download but not execute it, and the classid (the clsid for shockwave flash) convinced IE that it was worth downloading without doing a HEAD request to determine the file type.
I didn’t get this to work on Firefox 5, but I only tried it cross-domain… it would probably work in-domain.
July 8th, 2011 at 10:30 pm
Hmm, it stripped my html. Lemme try escaping it
<object classid=”clsid:d27cdb6e-ae6d-11cf-96b8-444553540000″ data=”http://cityvillefb.static.zgncdn.com/hashed/e22be2ab73010dd6afde5c39a37834ce.swf” declare=”1″></object>
July 18th, 2011 at 10:57 pm
[...] 書き込みの下の参そっちの一部を与えていたことがいくつもの関連議論のリンク*http://googlecode.blogspot.com/2009/09/gmail-for-mobile-html5-series-reducing.htmlだった09年のグーグルの文章が掲載された*http://www.phpied.com/preload-cssjavascript-without-execution/だった [...]
August 19th, 2011 at 2:25 pm
VPS…
Preload CSS/JavaScript without execution / Stoyan’s phpied.com…
August 30th, 2011 at 9:44 am
[...] Stoyan Stefanov wrote a great post with tactical information on pre-fetching. Here is his post: http://www.phpied.com/preload-cssjavascript-without-execution/ This entry was posted in Technical. Bookmark the permalink. blog comments powered by [...]
August 31st, 2011 at 10:16 am
[...] – 使用preload技术,除了loader外 [...]
September 7th, 2011 at 3:24 am
[...] 对于不在当前页面中的链接,如果需要预下载后续内容可以用js来实现,请参考这篇文章Preload CSS/JavaScript without execution [...]
November 18th, 2011 at 4:02 am
oxyhives prescription…
[...]Preload CSS/JavaScript without execution / Stoyan’s phpied.com[...]…
November 27th, 2011 at 6:36 am
[...] Preload CSS/JavaScript without execution – Pré-chargement des CSS et du Javascript sans exécution. [...]
November 28th, 2011 at 1:45 pm
[...] Preload CSS/JavaScript without execution – Pré-chargement des CSS et du Javascript sans exécution. [...]
December 14th, 2011 at 4:46 am
[...] 对于不在当前页面中的链接,如果需要预下载后续内容可以用js来实现,请参考这篇文章Preload CSS/JavaScript without execution [...]
December 14th, 2011 at 7:52 am
[...] 对于不在当前页面中的链接,如果需要预下载后续内容可以用js来实现,请参考这篇文章Preload CSS/JavaScript without execution [...]
December 23rd, 2011 at 5:25 am
I tried follow code,tested in ie8 chrome16 and ff8,ie and chrome do cache the javasctipt,bu i have no idea wether firefox do so for firebug don’t report wether the new js is from cache.
var names=[]//js to be loaded
var loadCount=0;
var loadCountl=names.length;
var img=new Image();//使用改变img对象src的方式加载所需js一次,放入缓存
img.onerror = function (a) {//当img加载完成后会触发onerror事件(不会触发onload,可能是由于mime类型不对)
if(loadCount0){//开始加载
loadJs(names[loadCount++])
}
function loadJs(packageName){//预加载js,使用了kola加载器的内部函数
var src=kola.Package._path(packageName);
img.src=src;
}
January 5th, 2012 at 3:13 am
[...] 对于不在当前页面中的链接,如果需要预下载后续内容可以用js来实现,请参考这篇文章Preload CSS/JavaScript without execution [...]