Short answer: One monolithic file with a far-future expires header.
Long answer: It depends. If you're Gmail and have over 1 megabyte of javascript in your application, then you're going to want to split that up to reduce load and improve user experience. For the rest of us though, the best solution I've found is to load your main library (such as jQuery) from a third-party CDN (like Google). Then split up your one monolithic file into two: lib.js
and app.js
. Lib should contain any third-party libraries, and app your site-specific code. The general idea being that you're very rarely going to be updating libraries or adding new ones, so that file will rarely ever have to be cache invalidated and re-downloaded by your users.
Use a technique such as the one described on this site to move almost all on-page <script>
tags without a src
attribute to an external file:
http://paulirish.com/2009/markup-based-unobtrusive-comprehensive-dom-ready-execution/
So what you should be left with are <script>
tags with a src
, and the only inline ones remaining should be because you need to pass data from your backend code to javascript (and want to avoid an AJAX request on page load that will block rendering). Say the namespace of my project is Foo
, then that one inline tag will look like:
<script type="text/javascript">
Foo.Context = {{ js_context|safe }};
</script>
js_context
is a variable I passed to my template from my backend, and is already a JSON object. The squiggly brackets and |safe
are just from the front-end template engine I happen to be using (in this case, Django's built-in templates). Now my page-specific code, living in app.js
, can look at the contents of Foo.Context
after document.ready
fires for any variables it needs while avoiding an initial AJAX request.
As usual for your static files, you should be serving them with far-future expires headers:
Cache-Control: public, max-age=31536000
When you update your scripts, your build step should either rename the concatenated and minified file or add a GET query parameter to the end of the <script>
src
, like so:
app.12345.js
or app.js?v=12345
That way your users will request the new file, breaking the cache. As a caveat, please be aware that old versions of some reverse-proxies (such as Squid) don't play nicely with GET parameters on static files, so the latter of those two options wouldn't work. But I have never run into that issue yet, so your milage may vary.
When you do get to the Gmail level of script sizes, you can start looking at using a library like LABjs or RequireJS, to dynamically only load the pieces of app.js
that you need for particular pages or functionality.
Last but not least, I hope your <script>
tags are at the bottom of your page, right before the closing </body>
. This prevents the browser from showing a blank white page while your scripts are downloaded, parsed, and executed. The only exception to this rule are libraries that absolutely must be in the <head>
to function, such as Modernizr.