Stop paying your jQuery tax

over 2 years ago

Reminder, script tags block rendering

It is common advice to move all your external JavaScript includes to the footer.

Recently, the movement for fancy JavaScript loaders like ControlJS, script.js and so on has also picked up steam.

The reason for this advice is sound and pretty simple. Nothing, Nada, Zilch below non asynchronous or deferred script tags gets rendered until the script is downloaded, parsed (perhaps compiled) and executed. With one tiny exception that is Delayed Script Execution in Opera. There are quite a few technical reasons why scripts block. You need a "stable" DOM while executing a script. Browsers need to make sure document.write works.

Often people avoid the async attribute on script tags cause they usually execute in the order they return this means you need to be more fancy about figuring out when the script is ready. The various JavaScript loaders out there give you a clean API.

Why is that jQuery synchronous script include stuck in the html 'HEAD' section?

jQuery solves the age old problem of figuring out when your document is ready. The problem is super nasty to solve in a cross browser way. It involves a ton of hacks that took years to formulate and polish. It includes weird and wonderful hacks like calling doScroll for legacy IE. To us consumers this all feels so easy. We can capture functions at any spot in our page, and run them later when the page is ready:

$(wowThisIsSoEasy); // aka. $(document).ready(wowThisIsSoEasy);

It allows you to arbitrarily add bits of rich functionality to your page with incredible ease. You can include little scripts that spruce up your pages from deep inside a nested partial. In general, people break the golden rule and include jQuery in the header. That is cause jQuery(document).ready is so awesome.

The jQuery tax

There are 3 types of tax you pay when you have a script in your header. The constant tax and the initial tax and the refresh tax.

The initial tax

The most expensive tax is the initial hit. Turns out more than 20% of the page views we get at Stack Overflow involve an non-primed cache and actual fetching of JavaScript files from our CDN. These are similar numbers to the ones reported by Yahoo years ago. The initial hit can be quite expensive. First, the DNS record for the CDN needs to be resolved. Next a TCP/IP connection needs to be established. Then the script needs to be downloaded and executed.

Browsers these days have a pack of fancy features that improve performance including: HTTP pipelining, speculative parsing and persistent connections. This often may alleviate some of the initial jQuery tax you pay. You still need your CSS prior to render, modern browsers will concurrently download the CSS and scripts due to some of the above optimisations. However, slow connections may be bandwidth constrained. If you are forced to download multiple scripts and CSS prior to render, they all need to arrive and run prior to render starting.

In the image below (taken from Stack Overflow's front page) you can see how the screen rendering could have started a 100 or so milliseconds prior if jQuery was deferred - the green line:

render

An important note is that it is common to serve jQuery from a CDN be it Google or Microsoft. Often the CDN you use for your content may have different latency and performance to the CDN serving jQuery. If you are lucky Microsoft and Google are faster, if you are unlucky they are slower. If you are really unlucky there may be a glitch with the Google CDN that causes all your customers to wait seconds to see any content.

The refresh tax

People love clicking the refresh button, when that happens requests need to be made to the server checking that local resources are up to date. In the screenshot below you can see how it took 150ms just to confirm we have the right version of jQuery.

This tax is often alleviated by browser optimisations, when you check on jQuery you also check on your CSS. Asynchronous CSS is risky and may result in FOUC. If you decide to render CSS inline you may avoid this, but in turn have a much harder problem on your hands.

The constant tax

Since we are all good web citizens (or at least the CDNs are), we have expire headers which means we can serve jQuery from our local cache on repeat requests. When the browser sees jQuery in the header it can quickly grab it from the local cache and run it.

Trouble is even parsing and running jQuery on a modern computer with IE8 can take upwards of 100 milliseconds. From my local timings using this humble page on a fairly modern CPU i7 960, the time to parse and run jQuery heavily varies between browsers.

Chrome seems to be able to do it under 10ms, IE9 and Opera at around 20ms and Firefox at 80ms (though I probably have a plugin that is causing that pain). IE7 at over 100ms.

On mobile the pain is extreme, for example: on my iPhone 4S this can take about 80ms.

Many people run slower computers and slower phones. This tax is constant and holds up rendering every time.

Pushing jQuery to the footer

Turns out that pushing jQuery to the footer is quite easy for the common case. If all we want is a nice $.ready function that we have accessible everywhere we can explicitly define it without jQuery. Then we can pass the functions we capture to jQuery later on after it loads.

In our header we can include something like:

window.q=[];
window.$=function(f){
  q.push(f);
};

Just after we load jQuery we can pass all the functions we captured to the real ready function.

$.each(q,function(index,f){
  $(f)
});

This gives us access to a "stub" ready function anywhere in our page.

Or more concisely:

<script type='text/javascript'>window.q=[];window.$=function(f){q.push(f)}</script>

and

Why have we not done this yet at Stack Overflow?

Something that may seem trivial on a tiny and humble blog may take a large amount of effort in a big app. For one, we need to implement a clean pattern for registering scripts at the page footer, something that is far from trivial. Further more we need to coordinate with third parties that may depend on more than a $ function or even god forbid document.write for ads.

The big lesson learned is that we could avoided this whole problem if we started off with my proposed helper above.

We spend an inordinate amount of time shaving 10% off our backend time but often forget the golden rule, in general the largest bottleneck is your front end. We should not feel powerless to attack the front end performance issues and can do quite a lot to improve JavaScript bottlenecks and perceived user performance.

Edit Also discussed on Hacker news thanks for the great feedback

Comments

Christophe over 2 years ago
Christophe

Could you provide a complete example (i.e. Your humble page) using this method so that JS beginners like me have it easier ?

Why not storing jQuery on the server providing the page content so that establishing another TCP connection would be avoided ?

Sam Saffron over 2 years ago
Sam Saffron

Actually this very page uses this technique, view the source.

Ricardo over 2 years ago
Ricardo

in larger websites the CSS usually dwarfs jQuery's download size, and if your site relies heavily on javascript it makes no sense to render anything before it can work. Good advice in general, just don't take it as absolute.

Sam Saffron over 2 years ago
Sam Saffron

the mobile tax is pretty big though and it is constant, for us at Stack Overflow CSS is a bit smaller than jQuery, page rendering is fairly stable without any need for jQuery stuff to assist in render prior to document ready. It is a valid point that some big sites have huge CSS files. Though I would argue that relying on JS for initial render is awkward and risky.

Sanjay over 2 years ago
Sanjay

In my case, I need JS for the page to be usable and also in css I have some big sprite images. I want to load sprite images after the JS is loaded. What should I do for that?
If i put JS in header, then nothing will appear on page till JS is downloaded. I dont want that.

–
Thanks
Sanjay

Phil_Parsons over 2 years ago
Phil_Parsons

Hows about running the onload just the once and running through the function list then, jsperf example





$(function() {
$.each(q, function(index, f) {
f();
});
Sam Saffron over 2 years ago
Sam Saffron

agree, that is faster, and just as short

Rich_Jones_Gun_Io over 2 years ago
Rich_Jones_Gun_Io

Sorry, why are you waiting? Do you let your ad providers execute arbitrary javascript on your website?

Great post though, love the detail.

Daniel_Baulig over 2 years ago
Daniel_Baulig

I think you should wrap that stuff in a closure, eg:

window.$ = (function() {


var q = [], f = function (cb) {
q.push(cb);
};
f.attachReady = function ($) {
$(function () {
$.each(q, function(i, f) {
f();
});
q.length = 0; // clear it, just in case
});
return $;
}
return f;
})();

// after jQuery loaded
$ = $.attachReady($.noConflict());

This will remove q from your global object and will also allow you to have a single, nice function call to attach your queued functions. The code is not exactly tested and just hacked into this form, but it should work out.

Sam Saffron over 2 years ago
Sam Saffron

wow, very nice trick ... safer.

Daniel_Baulig over 2 years ago
Daniel_Baulig

It doesn't seem to work as I expected. The $ from $.attachReady seems to be resolved before $.noConflict() is called. You would have to call $.noConflict() outside the call to attachReady, eg:

$.noConflict();


$ = $.attachReady(jQuery);

Not as nice and “functional”, but it'll do.

Jack_Moore over 2 years ago
Jack_Moore

@Phil Parsons. That was my first thought too, but you no longer have parity with a normal call to the ready method. To be safe, you should account for the execution context.

$(function() { $.each(q, function(index, f) {


f.call(document);
}); });
Sam Saffron over 2 years ago
Sam Saffron

good catch!

Phil_Parsons over 2 years ago
Phil_Parsons

@Jack, good call!

Steve_Hanov over 2 years ago
Steve_Hanov

It is worth mentioning that if you move your scripts to the footer, you may not need the heavyweight jQuery.ready. At that time the DOM is loaded and you can often safely perform your initalizations.

David_Murdoch over 2 years ago
David_Murdoch

I came up with something very similar back in July:

[How to Setup jQuery.ready Callbacks Before jQuery is Loaded][http://blog.vervestudios.co/blog/post/2011/07/01/How-to-Setup-jQueryready-Callbacks-Before-jQuery-is-Loaded.aspx].

 // in your head (zombie zombie zombie)


(function(a){
_q = function(){return a;};
$ = function(f){
typeof f==="function" && a.push(arguments);
return $;
};
jQuery = $.ready = $;
}([]));

// and after jQuery has loaded
(function( i, s, q, l ) {
for( q = window._q(), l = q.length; i < l; ) {
$.apply( this, s.call( q[ i++ ] ) );
}
window._q = undefined;
}( 0, Array.prototype.slice ));

This takes care of the following use cases:

$(elementOrSelector).ready(function(){});


$(function(){});
$.ready(function(){});
$(function(){});
Sam Saffron over 2 years ago
Sam Saffron

nice, still prefer the method above that captures the callbacks in the closure, but this offers more comprehensive support

Tb over 2 years ago
Tb

… interesting read, old subject in general, VERY misleading title. The problem described is very jQuery agnostic.

Cody_Allen over 2 years ago
Cody_Allen

This is probably ignorance on my part, but the section “Why is that jQuery synchronous script include stuck in the html ‘HEAD' section?” didn't really seem to answer that question for me. I'm sure there is a good reason, but I don't understand why we are doing this run-around instead of just keeping ALL JavaScript in the footer. Then as long as we load jQuery first, we can just use the jQuery.ready method directly, right?

Colin_Gourlay over 2 years ago
Colin_Gourlay

I love your solution for enabling $(handler), so I took it a step further and added support for $(document).ready(handler), $().ready(handler) and $(document).bind(“ready”, handler). I explain my solution in this blog post that I published minutes ago: Safely Using .ready() Before Including jQuery

Sam Saffron over 2 years ago
Sam Saffron

nice, I like it

Adam over 2 years ago
Adam

I put together what appeared to be the “revised version” on jsFiddle (collected from the article and comment contributions from Daniel Baulig and Phil Parsons). I threw in some console calls showing how things actually play out for anyone interested in order of execution (only one quirk, but it makes sense).

Kevin_Ball over 2 years ago
Kevin_Ball

Thanks for writing about this. Figuring out the “right way” to do javascript that is both performant and manageable is becoming so much more important as apps become more and more javascript heavy.

I'd be really interested to hear more about your thoughts on clean ways for registering scripts at the bottom of the page. Have you settled on an approach and just need to implement it? Or are still evaluating? Thanks

Zachary_Johnson over 2 years ago
Zachary_Johnson

I've actually considered this pattern for jQuery myself. The biggest problem is that common jQuery plugins are not generally defined inside of a jQuery DOM Ready call. They are defined immediately when parsed and depend on a fully defined jQuery object.

The pattern you show with pushing to a static array is of course the asynchronous pattern that Google came up with for their Analytics JS API. It is very useful for third party JS APIs, and I really wish that Facebook and Twitter would adopt a similar interface for their JS APIs for their social features.

It should also be noted that as of 1.7 jQuery actually implements an asynchronous AMD pattern. jQuery will actually let you know when it has loaded now. People should really consider trying Require.js http://requirejs.org/

Finally, in many cases, it would be far smarter and far less of a headache to compile and minify your JS sources into a single file that can be loaded at the bottom of your page body tag.

Peter_Bengtsson over 2 years ago
Peter_Bengtsson

So if I understand this correctly, this trick is ONLY useful if you can't wait with your various `$(function() { doSomething() }); until AFTER jQuery has been loaded.

Those of use who load jquery in the footer and THEN run various callbacks on $(function() {... don't need to do anything.

Right?

Anonymous_Coward over 2 years ago
Anonymous_Coward

What's the comment engine on this page?

Sam Saffron over 2 years ago
Sam Saffron

I wrote it in Ruby on Rails, the missing gravatars are robohash

Julien_W over 2 years ago
Julien_W

Cédric Morin made something like that called jQl for “jQuery loader”.

There is a github repository there : https://github.com/Cerdic/jQl

Sam Saffron over 2 years ago
Sam Saffron

There is a slight issue there, if you include the script in the header you still can cause your page to slow down on initial views.

Philip_Tellis over 2 years ago
Philip_Tellis

Why not use YUI? The YUI loader is extremely light weight, and loads everything else asynchronously.

Sam Saffron over 2 years ago
Sam Saffron

I have nothing against using various loader, as long as the include lives in the footer

Doug_Neiner over 2 years ago
Doug_Neiner

I am trying to understand just why this solution is being proposed – it seems to me, even when including in the footer, you just include jQuery before other scripts. What benefit is there in including other scripts before jQuery if they depend on its document ready function?

Either way, using a completely separate handler would be far superior than overwriting $ – many plugins attach additional information direction to the jQuery object making this solution only slightly helpful in very specific use cases. If they attach to $ or jQuery, it would be blown away when the actual jQuery loads.

Additionally, if your scripts occur just before the closing

tag, then they shouldn't need document.ready anyway.

Was the point of this solution due to async scripts that might return at any time, possibly before jQuery? (Haven't played much with async scripts like that, I just include scripts in the footer.)

Sam Saffron over 2 years ago
Sam Saffron

There are two use cases for such a solution (or more complete one proposed here: http://blog.colin-gourlay.com/blog/2012/02/safely-using-ready-before-including-jquery/ )

  1. Migration from a system with inline js sprinkled through the pages to one where all the scripts are in the footer. In general, deferring external includes (including plugins) is fairly simple compared to deferring inlines from what I have seen in both rails and asp.net mvc. From all my uses of jQuery I always capture functions behind document ready prior to interacting with jQuery.

  2. Cases where you are defer loading jQuery itself, stubbing $ is perfect there, cause you still get a very clean and consistent API, even while jQuery is loading.

It is possible a separate handler would work better for your specific use case, it is possible that you may not need the hack at all if you simply push stuff to the footer.

I am unclear about the need for document.ready (or not) if you are the last script on the page, you would have to test this on multiple browsers to get a definitive answer.

David_Higgins over 2 years ago
David_Higgins

I use a trick to handle this problem: set a variable at the very end of each JS file to true E.g:

… some code…
ready = true

In the host document (which contains the script src block), you have a global variable set to false, e.g:

var ready = false;

Then just check to see if the script has loaded using setTinterval();

E.g:

setInterval(function(){

if(ready =true){

alert(‘script has loaded')

}

}
)

Only caveat here is each script.js has to have a unique variable, and a unique setInterval isLoaded? checker function, but you can easily abstract this away with PHP, or JS itself.

What this does is solve the issue scriptloaders have been trying to solve for years..

“Is my script loaded, and if so, when can I execute code inside it”

Draco_Blue over 2 years ago
Draco_Blue

Hey,

I would argue that registering all your nice plugins with a $(selector).yourFunkyPlugin() for every plugin is problematic. We developed a nicer way to register your “plugins” in a light weight way, and executing the plugin logic only if a match for the selector really exists. It's called jsb, comments appriciated: https://github.com/DracoBlue/js-behaviour.

Tero_Teelahti over 2 years ago
Tero_Teelahti

You do not need to use the ready event if scripts are included in the bottom. I've used this extensively and never had any problems with it.

Sam Saffron over 2 years ago
Sam Saffron

One subtle point is that defer scripts will run prior to ready see: http://www.w3.org/TR/html5/the-end.html#the-end

Dmitry_Pashkevich over 2 years ago
Dmitry_Pashkevich

So why don't you use ControlJS, script.js or also popular LABjs to do the routine of loading other scripts in the desired order?

Placing the loader in the

section (it's only around 5kb without gzip and around 2kb with gzip!) and not having to rearchitect your entire system (the footer include pattern you mentioned) sounds like a really good compromise, no? I'm really curious to know why it was discarded?

Dmitry_Pashkevich over 2 years ago
Dmitry_Pashkevich

Oh, the HTML got trimmed, I meant “Placing the loader in the HEAD section…”.

Sam Saffron over 2 years ago
Sam Saffron

Dmitry totally valid, if you are using script loaders you are fine provided you are loading the script loader async.

Juraj_Vitko over 2 years ago
Juraj_Vitko

Perhaps another / complementary way to approach this:

http://code.google.com/p/domready/
1.35KB minified with Google Closure Compiler (simple mode)

https://github.com/ded/qwery
5.98KB minified

Vic over 1 year ago
Vic

I'm still stumped why would I need this and is it a real problem? I've been working in a commercial web development environment for the last more or less 5 years and I don't think I ever needed to put jQuery after any of the libraries I wrote? There is one great solution to this issue I guess… The one I've been using for a long time… Use vanilla JS.

Alex 11 months ago
Alex

Sorry to comment on an old post, but…

I’m loading jquery using the DEFER-attribute to speed things up even more. But I can’t figure a way to run this $.each(q,function(i,f){$(f)}) part after jQuery has been loaded. How do I tell when a “defered” script has been loaded? Tried googlin, found nothing :(

Dave Sumter 6 months ago
Dave Sumter

We could leave out the reference to window because we are in global scope, right...?

<script type='text/javascript'>q=[];$=function(f){q.push(f)}</script>

Great topic. Using this pattern live as we speak..

Sam Saffron 6 months ago
Sam Saffron

Yeah, good point, that should be fine.

Robert Corsari 6 months ago
Robert Corsari

Hello,
I'm wondering why this post is 2 years old.

Or the issue has been solved at the source, or?

Anyway, google page speed test complains because my website doesn't load asynchronously the js scripts.

One of them is jquery. In the mean time, even before thinking about asynchronous, since I'm using this jquery responsive navigation menu http://www.smartmenus.org/, I noticed that setting jquery at bottom of the page, makes the jquery menu to doesn't display the nested li(s)

Can you kindly hint with a working solution? Also think about many hosting are slow, so loading jquery from a CDN is still quite a must.

Thank you for a working/full example thought for not-js-professionals

Obviously the working example is meant as example with that jquery menu or modal popups lightbox-like (showing pictures or embedding youtube videos), accordions, sliders etc

Cor.

Sam Saffron 6 months ago
Sam Saffron

I am not following, this blog post is a working example.


comments powered by Discourse