Loading External Resources Dynamically, Asynchronously and Conditionally Using JS

These several days I've been spending time figuring out the best way how to load external resources into HTML dynamically. In conclusion, there are 2 ways to accomplish this: through inserting tags like <script> and <link>, or through XMLHttpRequest; but code for that is mundane to write, there should be an easy way to accomplish this…

It's all started when I was writing a code using Three.js, a graphical JS library supporting WebGL API, where I started searching for a good way for loading external CSS and JS files. Then my attention shifted to yepnope.js, a JS library for an asynchronous conditional resource loading.

Although yepnope.js can asynchronously download external resources, there is a nasty side effect at least in Safari (ver. 7.0 (9537.71)): two duplicate resource instances for one actual resource; a JS file was downloaded as an image first, then executed as a JS script, so technically one image resource and one JS resource were produced in the browser document for one JS file. Looks like this odd side effect is the result of an implementation decision to enforce a specified execution order of loaded JS script. For I don't like this side effect, I turned to jQuery.


Nonetheless, I'll show you a yepnope version for loading SyntaxHighlighter, then a jQuery version.

yepnope({
  load : path('@shCore.js'),
  complete : function() {
    // Now SyntaxHighlighter object is available,
    // ready to load brushes.
    // Good for debug: SyntaxHighlighter
    var paths = path('@shBrushCpp.js', '@shBrushBash.js', '@shBrushXml.js', '@shBrushJScript.js', '@shBrushCSharp.js', '@shBrushPython.js', '@shBrushCss.js');
    yepnope({
      load : paths,
      complete : function() {
        // All the specified brushes were loaded,
        // now it's time for syntax highlighting
        // *ONLY* if document.body was already
        // loaded.
        // Good for debug: SyntaxHighlighter.brushes
        shLoaded();
      }
    });
  }
});
yepnope version for loading SyntaxHighlighter

The benefit of yepnope is its intuitional, human-friendly interface; even though I didn't use 'test', 'yep' and 'nope' in the code above, the library lets users easily know to which variable/functionality they are assigning a value/function. Also, a capability to receive data in arrays or associative arrays is reducing users' mind labor.


$.getScript(path('@shCore.js')).done(function() {
  // Now SyntaxHighlighter object is available,
  // ready to load brushes.
  // Good for debugging: SyntaxHighlighter
  var paths = path('@shBrushCpp.js', '@shBrushBash.js', '@shBrushXml.js', '@shBrushJScript.js', '@shBrushCSharp.js', '@shBrushPython.js', '@shBrushCss.js');
  $.when.apply(null, paths.map($.getScript)).done(function() {
    // All the specified brushes were loaded,
    // now it's time for syntax highlighting
    // *ONLY* if document.body was already
    // loaded.
    // Good for debug: SyntaxHighlighter.brushes
    shLoaded();
  }).fail(function() {
    console.log('error: failed to load SyntaxHilighter bruses');
  });
}).fail(function() {
  console.log('error: failed to load SyntaxHilighter core');
});
jQuery version for loading SyntaxHighlighter
Sidenote: if paths = [url1, url2] then $.when.apply(null, paths.map($.getScript)) is simply equal to $.when([$.getScript(url1), $.getScript(url2)]).

Although jQuery is not as competitive in usefulness for loading JS and CSS files as yepnope, it does have a capability to load JS files asynchronously (it's failing to do so for CSS though). Functionalities enabling jQuery to do so are $.ajax() (or its shorthands $.get() or $getScript()), $.Deferred and $.when(). To delve deeper, jQuery has a specific hack for loading JS resources in $.ajax(); it does not use AJAX at all (yes, no data fetching with JS), and farm the the whole JS loading task out to its web browser by inserting a set of tags <script src="…url…"></script> into the document (you can find a code in question around comment line "// Bind script tag hack transport" in jquery.js v1.10.2).

I like the concept of deferred objects and how they work, but it seems my understanding of deferred objects is wrong and/or above jQuery code is buggy; especially when I'm using Web Inspector for monitoring network requests and alike in real time, sometimes not often the code fails to wait certain JS or CSS to be loaded (thus error outputs like "Can't find brush for: js" or broken layout in SyntaxHighlighter). I feel like overlooking some event handling on timeout or failure, hmm…


And what about the Three.js code? This was quite a detour, I even learned how to use Wireshark, an open-source packet analyzer, to monitor HTTP requests for this!

Comments

Popular posts from this blog

Hardcoding Data Like Images and Sounds into HTML or CSS

DDS for GIMP (Mountain Lion, Native, no X11 GUI)

Make It Work: Global .gitattributes