DailyJS

DailyJS

The JavaScript blog.


Tagloaders
Featured

code-review loaders

Code Review: LABjs

Posted on .

*Code Review* is a series on DailyJS where I take a look at an open source project to see how it's built. Along the way we'll learn patterns and techniques by JavaScript masters. If you're looking for tips to write better apps, or just want to see how they're structured in established projects, then this is the tutorial series for you.

About LABjs

LABjs (GitHub: getify / LABjs, License: MIT) by Kyle Simpson
is an asynchronous script loader. Although there are many similar
projects now on the scene, LABjs is the first that I used heavily.

LABjs by default will load (and execute) all scripts in parallel as fast as the browser will allow. However, you can easily specify which scripts have execution order dependencies and LABjs will ensure proper execution order. This makes LABjs safe to use for virtually any JavaScript resource, whether you control/host it or not, and whether it is standalone or part of a larger dependency tree of resources.

It has a nice little chained API:

$LAB.script('https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js').wait()
    .script('/javascripts/example1.js').wait()
    .script('/javascripts/example2.js');

Inserting wait() into the chain can be used to express a
dependency -- in this case example2.js won't run until the
other scripts have been fully loaded.

Structure

LABjs is distributed as a single script that currently weighs in at 513
lines of code. The project also comes with tests and a minimised file.
This project is religiously indented with tabs, and it looks best with a
tabstop of 4. This project doesn't ship with its build scripts, so I
can't duplicate the minification process.

As we've seen many times in this series, the self-executing anonymous
function is used to contain the entire library:

(function(global){
  var _$LAB = global.$LAB,

  // ...

  global.$LAB = create_sandbox();
})(this);

Presumably the author has chosen to export the library as
\$LAB to avoid collisions with LAB, but I
can't imagine using that name for anything anyway.

One thing I noticed early on was this debug style:

/*!START_DEBUG*/
  // console.log() and console.error() wrappers
  log_msg = function(){}, 
  log_error = log_msg,
/*!END_DEBUG*/

This explains the presence of LAB-debug.min.js and LAB.min.js. It would
be useful if the build process was present in the form of a build script
so I could build my own versions of these scripts.

Feature Sniffing

Generally, LABjs opts to use feature sniffing to gather browser
capabilities:

// feature sniffs (yay!)
test_script_elem = document.createElement("script"),
explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29
real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append?
script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order

// XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers)
xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko

This is preferable to branching based on the user agent string. I
wondered if test_script_elem needs to be discaded to
avoid leaking a DOM element, but I'm fairly sure even older IEs will
garbage collect it because no properties are changed and there aren't
any circular references.

Support Functions

LABjs is developed to work without any particular client-side framework,
which means it includes a few commonly found helper functions:
is_func, is_array, and
merge_objs jumped out at me. Is it time for someone to
make a "native JavaScript helper function" library?

Chained API

When using LABjs, we're actually interacting with the
create_sandbox function. This contains several variables
to manage state, functions for managing the chained API and requests,
and it returns an object that forms a public API. Here we can see how
wait works:

wait:function(){
  return create_chain().wait.apply(null,arguments);
}

It actually creates new chains, then calls the wait method
on the chained API. Following this through illustrates how requests and
dependencies are managed:

// force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding
wait:function(){
  if (arguments.length > 0) {
    for (var i=0; i

This is a fairly typical way of managing a chained API. Afterwards,
advance_exec_cursor is called and the current chained API
object is returned so subsequent calls to script() can be
performed. The trick to making a natural chained API is working out
where code can be executed or deferred. In this case,
advance_exec_cursor is used to execute parts of the chain
in the right sequence.

Ultimately, do_script will be called by
script, which just sets up the request. A registry of
loaded scripts will be built up, and it'll optionally insert parameters
to force caches to be ignored.

If a script requires pre-loading, setTimeout is used to
immediately call the ready listener.

request_script, called by do_script will
attempt to load the script using the most suitable method.

Request Handling

Inside request_script, setTimeout is used to
run the body of the function. This is apparently to avoid race
conditions in older browsers. Another setTimeout is used to
wait until the element where the script tags is pasted is ready. The
options provided by do_script in script_obj
are used to create a script tag and insert it into the desired element.

Once the script tag has been set up, the real cross-browser code kicks
in that has to manage the callbacks needed to monitor the script's
network events and ready state. Like many mature client-side projects,
this requires some careful handling of browser kinks.

Conclusion

LABjs isn't a trivial library, but it can make web pages feel fast
without concatenating all of the client-side JavaScript into monolithic
files. On reviewing the code I found several areas that had interested
notes about cross-browser support and proposals for more modern
solutions.

Some of the core methods in the library have very high arity. Using
option objects might make these functions clearer. It would also be nice
to keep the build scripts in the repository.

Featured

code-review loaders

Code Review: jLL

Posted on .

*Code Review* is a series on DailyJS where I take a look at an open source project to see how it's built. Along the way we'll learn patterns and techniques by JavaScript masters. If you're looking for tips to write better apps, or just want to see how they're structured in established projects, then this is the tutorial series for you.

I usually select code review subjects based on what I use in my own
projects. However, this week Mihai Bojin sent in a project:
jLL (JavaScript Library Loader). This is the first user-submitted code review -- why not send in your own
projects?

About jLL

jLL (License: MIT) by Mihai Bojin is a lightweight script loader. It can either load scripts
asynchronously or in predetermined order.

Usage

Scripts can be loaded by pushing them onto a queue with a callback:

var deferrer = jLL.staticDeferrer();

deferrer.push('jquery-1.6.1.js', function() {
  // Done
});

deferrer.run();

It's also possible to load when the page is ready, using
deferrer.runOnLoad(). To load jQuery then several other
libraries, the following pattern can be used:

var deferrer = jLL.staticDeferrer();

deferrer.push('jquery-1.6.1.js', function() {
  var asyncLoader = jLL.buildScriptDeferrer();

  // Push a number of scripts to be loaded in order
  asyncLoader.push([
    ['js/jquery.roundabout-1.0.min.js'],
    ['js/jquery.roundabout-shapes-1.1.js'],

    // after final script is loaded attach onReady event
    ['js/contact.js', function() {
      // Ready
    }]
  ]);

  asyncLoader.run();
});

deferrer.runOnLoad();

Structure

The project is distributed with the source file, a minified version, a
big set of examples, and some tests. It's really great to see tests
distributed with a project like this, so I'll take a deeper look at
those later.

The main source file itself includes solid documentation, which makes it
very easy to navigate the code. The self-executing anonymous function
pattern is used yet again, this time passing in window and
document:

(function (window, document) {
    "use strict";

  var jLL,  
        head,
        ScriptTag,
        ScriptDeferrer;

  // ... snip

    // attach an instance of the jLL object to the window
    window.jLL = jLL;
}(window, document));

Mihai has placed everything inside the anonymous function in strict
mode
. This is
something that's starting to crop up more and more in the client-side
community.

Next, ScriptTag and ScriptDeferrer are defined
as simple prototype classes. Most of the public methods are added to
this inside the constructor, which means they're
"privileged" in that they can see the private members of the class:

ScriptDeferrer = function () {
  var queue,
    running,
    loadFromQueue,
    raceFromQueue;

  // ... snip

  this.push = function (src, onload, check, checkTrigger) {
    var i, 
      tag,
      allowContinue;

Now push can access queue without exposing
queue as a public property. In general, Mihai is careful about what's
being exposed throughout this project.

Other parts of the library, like script tag rendering, are nicely
encapsulated by suitable methods.

Tests

The tests are written with QUnit so
they'll run in a browser. The tests are organised nicely using modules,
grouped according to the main classes in the project:
ScriptTag and ScriptDeferrer.

A helper file is used to keep a lot of useful mocking functions outside
of the main test file. Most of these are simple functions that are
passed to jLL as callbacks:

var mockCheckEventTrue = function() {
  ok(true, "Mocking check and returning true");
  return true;
}

test("Check passes and loads object", 2, function() {
    var script = jLL.buildScriptTag('mock_load/mock_add_2.js');
    stop();
    script.addCheck(mockCheckEventTrue);
    script.addEvent(mockOnloadEvent);
    script.addEvent(start);
    script.render();
});

Style

I think my tabstop was set to something that made this project's spacing
look weird, so I had to tinker with that. It's probably worth
remembering that if you like to line up var declarations,
then mixing tabs and spaces defeats the point of using tabs if people
aren't using the same tab spacing as you:

// What you want:
var a,
    b,
    c,;

// What someone might see with a different tabstop:
var a,
      b,
      c;

// Or:
var a,
  b,
  c;

Mihai is another fan of placing literals first in conditionals:

if ("object" !== typeof src[i] || -1 === src[i].constructor.toString().indexOf("Array")) {
}

Notice he's also carefully using the right comparison operators.

Conclusion

This project would definitely benefit from a simple website with some
easy to follow examples and automatically generated API documentation.
Also, the minified version is distributed with no reproducible build
script. Including a simple Makefile is a good idea for this
type of project, just so the author or contributors are less likely to
forget to minify the latest version.

These might seem like high-level project-management issues, but it's
worth keeping these things in mind when a project starts to mature and
the forks/contributions start rolling in.

If this was my project I'd consider a simplified chained API, and
removing the window onload functionality:

jLL
  .push('script1.js')
  .push('script2.js');

I'd also consider removing .run, and add a method to the
chain that can handle waiting for callbacks:

jLL
  .push('script1.js')
  .wait()
  .push('script2.js');

However, that line of thinking ends up being suspiciously similar to
LABjs, and I expect the author has his own reasons for making it different.