Javascript – Storing page-specific javascript on an AJAX driven site

javascriptmvc

I have a general question about the placement of javascript code in a AJAX-driven web application. At a previous job, we tended to throw everything in one monolithic file. When maintaining that file became too difficult, we split it up into multiple smaller files and "glued" them back together with a php "loader" (pretty much just a bunch of include statements)

Since all of my pages are dynamically loaded through AJAX, I cannot simply have different files called separately per page.

The obvious disadvantage to this method is that all of your javascript is loaded by the end user, even if it is not required on first page load. (Which compounds the problem, the more pages you have)

To get around this, I started putting page-specific javascript on the template page itself, in a <script> tag at the bottom, which works as intended. Only the javascript I want is loaded.

However, I am unsure of whether this practice is taboo, or if there are better strategies for loading my javascript. I get the feeling that mixing my template with my scripted code is a recipe for disaster..

Best Answer

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.

Related Topic