6
\$\begingroup\$

Edit: Even adding a comment of "looks good to me" and up-voting that comment would be appreciated :) I just want the feedback! Thanks!

For the fun of it as well as professional development, I wrote a small "library" (single 24 line JavaScript function) that will allow a developer to double check that all external libraries loaded and to reload them with a local copy as a fallback if the CDN is blocked or down.

Double Take Script Loader

Double Take Script Loader

This is a simple way to load a local copy of a library as a fallback to an external CDN.

I'm looking for feedback on my methods. Here's the actual gist on GitHub. Is there any (obvious) way I could improve this function? I realize that everyone has their own style and such. I'm not asking about that. I'm asking about better performance/ease of use. See the specific questions on the lines of code.

dt-script-loader.js

var DoubleTakeScriptLoader = function(scripts) { //I have to use the global namespace if I want to let devs call it on their own right?
  if (scripts) { //Allows devs to call it with specific scripts
    if (Object.prototype.toString.call(scripts) !== '[object Array]' ) { //Is there a better way to check whether it's an array without using another library?
      scripts = [scripts]; //Allows devs to call with just an object and I'll change it to an array
    }
  } else {
    scripts = document.getElementsByTagName('script'); //If function is called without argument, we'll get the scripts from the document. I think this will only load the scripts that have been parsed thus far correct?
  }
  for (var i = 0; i < scripts.length; i++) {
    var script = scripts[i];
    var propName = script.propName || script.getAttribute('data-prop-name'); //For use without calling with an argument, I get a data attribut. Is getAttribute the best way to get attributes or should I use dot notation in the context? I have seen differing opinions.
    var localSource = script.localSource || script.getAttribute('data-local-src');
    if (propName != null && localSource != null) {
      if (!window[propName]) { //If the library was not loaded. Is there any way to be aware that the request to get the library failed so this can be responsive to failures rather than called regardless?
        document.write('<script src="' + localSource + '" type="text/javascript"><' + '/script>'); //I tried creating a DOM element and adding it to the DOM, but the script wouldn't load. Did I do something wrong, or is this the best way to add a script to the DOM as it is parsing?
        if (script.originalId) {
          script = document.getElementById(script.originalId);
        }
        !script.parentNode || script.parentNode.removeChild(script); //Remove the old script. Good/bad?
      }
    }
  }
};
DoubleTakeScriptLoader(); //Invoke the script so devs using it don't have to do so. Should I allow them to have a data- attribute on the script itself which says "don't invoke?" I don't see it being any performance issue or anything....

Thanks for any and all input. I realize that a single function is not much of a library, but I'm learning and I want to build something useful for others.

Example

For an example of how to use this function, see example.html. You will of course need local copies of jQuery and Underscore because for the purpose of the example the CDNs do not actually exist. You can also look at the demo.

example.html

<!DOCTYPE html>
<html>
  <head>
    <title>DT Script Loader</title>
  </head>
  <body>
    <h1>Double Take Script Loader Example</h1>
    <div id="message-jQuery">
      Waiting to load jQuery...
    </div>
    <div id="message-underscore">
      Waiting to load Underscore...
    </div>
    <!-- Method 1: Use data- attributes. This does NOT require calling DoubleTakeScriptLoader, just including it. -->
    <script src="http://cdn.example.com/underscore/1.4.4/underscore-min.js" data-prop-name="_" data-local-src="underscore-min.js"></script>

    <!-- Method 2: Call DoubleTakeScriptLoader directly -->
    <script src="http://cdn.example.com/jquery/1.10.1/jquery.min.js" id="jQueryScript"></script>
    <script src="dt-script-loader.js"></script>
    <script>
      DoubleTakeScriptLoader({
        propName: '$',
        localSource: 'jquery-1.10.2.min.js',
        originalId: 'jQueryScript'
      });
    </script>

    <!-- Use libraries. This must be done AFTER calling DoubleTakeScriptLoader() -->
    <script>
      (function() {
        var checkStuff = function(prop, elementId) {
          var el = document.getElementById(elementId);
          if (window[prop]) {
            el.innerText = 'Loading ' + prop + ' worked great!';
            el.style.backgroundColor = "green";
          } else {
            el.innerText = 'Loading ' + prop + ' did NOT work!';
            el.style.backgroundColor = "red";
          }
        };

        checkStuff('$', 'message-jQuery');
        checkStuff('_', 'message-underscore');
      })();
    </script>
  </body>
</html>

I, the student, thank you for your time and feedback and look forward to your constructive input.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I like the look of this in general. Its short, simple and I'd love to know how well its worked in the field.

I'm not a big fan of document.write but I don't think the alternative is much better. If you look in the jQuery source code you'll see they eval the script.

It comes from http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context

In place of the document.write I might put something like:

var xmlHttpReq = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xmlHttpReq.open('GET', localSource);
xmlHttpReq.setRequestHeader('Content-Type', 'application/javascript');
xmlHttpReq.onreadystatechange = function _readyStateChange() {
    if (xmlHttpReq.readyState == 4) {
        (window.execScript || window.eval).call(window, xmlHttpReq.responseText);
    }
}
xmlHttpReq.send(null);

This uses an ajax request to get the JavaScript instead of blocking your page load. Its all a matter of taste I believe. (Also any other scripts might fail if they are expecting your framework to have loaded.)


After some Google Searching I've found a few places that do the almost same as your framework in less code.

<script src="/path/to/cdn/framwork.js"></script>
<script> window.framework || document.write("<script src=\"/local/path/framework.js\"></script>");</script>

It is shorter but requires one per script file.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.