Skip to content

Instantly share code, notes, and snippets.

@3n
Created May 11, 2011 18:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save 3n/967051 to your computer and use it in GitHub Desktop.
Save 3n/967051 to your computer and use it in GitHub Desktop.
A "blog" "post" about asynchronous script loading and MooTools.

A lot of talk at JSConf was on the use of modules: AMD, CommmonJS, or otherwise. Since the server-side JavaScript world has this mostly covered, a more heavily discussed topic was bringing the joys of modularity to the client-side. This, of course, is a tricky thing to do since loading scripts requires some sort of relatively slow and possibly asynchronous network request. How does one balance the issues of perceived latency, JS execution (UI blocking), page weight, and code tidiness? I spent some time looking into this and here's my first stab at a solution (no I'm not releasing some open source project shut up).

Real quick: if you don't agree that splitting files to do asynchronous loading is a good idea you should read more.

I code most of my projects in MooTools (including my current project, BankSimple), so modularity is a given for me. MooTools is highly modular, with well-mapped dependencies between over 100 files (classes, typically). My usual strategy is to use packager to build my main app file – which has a list of requires from MooTools more/core, plugins and my app code files – into a single app.js file. I knew I wanted to take advantage of asynchronous module loading on the client-side, but there were a lot of approaches to consider:

  1. Write a script to convert every MooTools module into some sort of wrapped AMD/CommonJS module that defines its provides/requires in code rather than YAML. Make sure each file of my application code is wrapped in a call to 'require([packages], function(){[my code]});' (or whatever) when I need a given module/class.
  2. Use one of the maybe five thousand asynchronous script loaders to load un-wrapped (raw, as-is) files/classes in on-demand.
  3. Split up my app.js into more, core, plugins and app files, load them asynchronously.
  4. Something that isn't asinine.

Approach #1 is obviously insane/wrong. Both of the first two approaches lean way too hard into having modules, and the thought of all those connections being made and the sheer number of spinner.gif's necessary to pull that off frightens me. Approach #3 is more reasonable, but that split of files is based entirely on code structure, not application need. I went with approach #4. I decided to split up my code into three types of files:

  1. init.js: a single script that is downloaded and executed along with the page. This is for code that actually builds your page, or needs to be executed before domready. This is optional if you don't need any scripts to build your page. Dropping this will improve your perceived latency a shit-load.
  2. add.js: a single file that is loaded asynchronously right after init.js and executed when it arrives. This should be used for non-visual but still important things like adding click/scroll events and setting up tooltips and what have you.
  3. feature.js: zero or more script files that define a given feature. In my case, these are loaded asynchronously on page load but not evaluated until they are needed read this.

As you can see, the files are split up based on need. To accomplish this split, I used my fork of packager, which has an added option to the build command (-files+deps file1 file2) that will build a package but not include any components from a given file or any file that it depends on. Here is what a typical build script can look like:

packager build MyApp/Init > init.js
packager build MyApp/Add -files+deps MyApp/Init > add.js
packager build MyApp/Feature -files+deps MyApp/Init MyApp/Add > feature.js

The reason for the -files+deps is so you don't include any code that's already going to be on the page. This, relies on the fact that you know the order in which the scripts will be included. That's where our script loader comes in.

After spending nearly a day and a half looking into the multitude of loaders, I narrowed it down to ControlJS, YepNope and curl. Narrowing it down to these three wasn't an accident: they are the only three (that I know of) with the built-in ability to defer script execution (not just download). Having the ability to load a script asynchronously during page load but execute on user-demand is huge. It makes your script loading essentially free. That means no waiting for the script to load and execute when the user clicks a button. That also means that three's no execution cost on page load for code you don't need right away. I'm currently using YepNope, which has some other nice features and a good API while still maintaining a light codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment