How and Why We Use Dependency-Free JavaScript at Janrain

Now, don’t get me wrong: I’m a huge fan of hefty, all-in-one JavaScript frameworks. Like a lot of other folks, jQuery was my first brush with real front-end development, and since I began learning jQuery back in the day, I’ve since come to harbor something between admiration (Backbone) and unrestrained adulation (AngularJS, jQuery Mobile) for some of the major JavaScript frameworks on offer nowadays.

It’s scarcely believable just how powerful and comprehensive these tools have become given the state of JavaScript just a half-decade ago. If I were building any kind of heavily event-driven client-side application with a lot of fancy UI elements and a structured data model–essentially what we now call the “thick client”–I would still opt to build it on top of a big MVC (or MVVM or MOVE or whatever) framework. The thought of building a thick client using the naked DOM API by itself is, to put it mildly, a slightly less than pleasant one.

Another path

Yet in spite of the wonders of contemporary JavaScript, there are times when clearing away the underbrush and getting back to the DOM API–the closest thing to bare metal that you’ll get in JavaScriptLand–is absolutely the only way of doing things.

When the original Janrain development team started making heavy use of JavaScript some years go, this was precisely the conclusion to which they came. For our use case–which involves allowing developers to simply copy and paste a JavaScript widget into their application that then enables social login and/or user registration and data storage–reliance on jQuery or Underscore or anything beyond native JavaScript was simply a non-starter.

Why? Because we have to write JavaScript that plays nicely with anything and everything with which it interacts. We can’t afford namespace or dependency clashes. Organizations that want to use Janrain Engage or Capture need to be able to drop a small snippet of JavaScript into their application (a snippet like the one included below) and to have it work unproblematically and without any additional work on their part…ever. Both the drop-in widget and the JavaScript that gets piped through the widget (more on that in a minute) have to demand nothing from the developers using Janrain. Our entire business model depends on it.

Another core reason: performance. The JavaScript that we compose that ends up in others’ applications has to be blazingly fast, and it has to work on as many devices and for as many use cases as humanly possible. It has to be as close to 100% agnostic toward its end destination as the DOM API allows for.

If we had chosen to use framework-heavy JavaScript, it would have meant two things for us:

  1. The good: it would have made our lives a lot less complicated because it’s far easier to find and then hire people to write jQuery or Backbone with most of the guts of JavaScript abstracted away.
  2. The bad: it would have made our lives vastly more difficult because we would have to require customers to go back and re-code all of their front-end JavaScript to ensure that it plays nicely with their supposed “drop-in” social login widget.

For customer-facing JS, this path was unacceptable, and so we decided to go all in on all-native JS.

Dependency-free JS example: drop-in widget

While Janrain uses a lot of libraries like Knockout, jQuery, and others in building dashboards and other thick client-style applications, all of the JS that we write for customers is dependency free (although we do offer widgets that depend on jQuery, but only if our customers ask for it). I could give hundreds of code examples here, but I’ll stick with the most stripped-down widget that we offer, simply in the name of offering a taste of the mechanics of low-level JS to the uninitiated.

This widget basically acts as a portal into the DOM for the more feature-rich JavaScript that powers a social login interface. Let’s have a look at what such a widget might look like in its entirety and go from there:

<script type="text/javascript">

  (function() {
    if (typeof window.janrain !== 'object') window.janrain = {};
    if (typeof window.janrain.settings !== 'object') window.janrain.settings = {};

    janrain.settings.tokenUrl = 'http://www.your_site.com';

    function isReady() { janrain.ready = true; };
    if (document.addEventListener) {
        document.addEventListener(DOMContentLoaded, isReady, false);
    } else {
        window.attachEvent('onload', isReady);
    }

    var e = document.createElement('script');
    e.type = 'text/javascript';
    e.id = 'janrainAuthWidget';

    if (document.location.protocol === 'https:') {
        e.src = 'http://rpxnow.com/js/lib/luc/engage.js';
    } else {
        e.src = 'http://widget-cdn.rpxnow.com/js/lib/luc/engage.js';
    }

    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(e, s);
  })();
</script>

 
First off, notice that this is a self-executing snippet of JavaScript. Any time you’re working in JavaScript and you see something of the form

(function() {
    ...do stuff here…
})();

 
you know that you’re dealing with something that isn’t going to need to be called from outside of the function. So this sets up the widget to execute itself as soon as its loaded.

Now, let’s see what’s actually going on in this self-executing function. In the first line, we see

if (typeof window.janrain !== 'object') window.janrain = {};

 
This is essentially a kind of guard to check if there’s already a window.janrain something-or-other floating around in the DOM. If there is, and it isn’t an object, it needs to become one. And so whatever that window.janrain is will immediately be converted into an empty hash (i.e. a JavaScript object). After that, the widget does the same check regarding any window.janrain.settings. In these first two lines, the widget basically ensures that we’re starting from the right place with an object of this form:

{
  window: {
    janrain: {
      settings: {}
    }
  }
}

 

Working with our empty window object

Now, we can begin populating this somewhat barren object. First, we need to fetch the tokenUrl from the application-specific URL (a URL that we provide you when a new Janrain-powered application is created). This URL is used not by the widget but rather by the JavaScript that will arrive in the application a few milliseconds later.

Next, we define an isReady() function to be used later. This function, when invoked, declares that the document is ready to go, so to speak. It should remind you immediately of the $(document).ready() function from jQuery. What that means in jQuery is that anything that happens inside of .ready( … ) happens as soon as possible and doesn’t wait for the page to load.

In the next line, we’ll see why this is important:

if (document.addEventListener) {
  document.addEventListener(DOMContentLoaded, isReady, false);
} else {
  window.attachEvent('onload', isReady);
}

 
This is here to ensure cross-browser compatibility. It says that if the addEventListener function is invoked, it will take the form either of the addEventListener function understood directly by Chrome, Firefox, Safari, and newer versions of Opera and Internet Explorer, or of the attachEvent function understood by older browsers. And so no matter what the browser, an event listener will be added to the document at large that basically waits until all of the DOM content is loaded (the onReady event for older and DOMContentLoaded for newer browsers) and then declares the janrain object ready (janrain.ready = true).

Piping JavaScript into the DOM from outside

So far, the widget hasn’t really done anything to modify the DOM in any significant way. In the next code block, that all begins to change:

var e = document.createElement('script');
e.type = 'text/javascript';
e.id = 'janrainAuthWidget';

 
Here, the widget is inserting a new <script type='text/javascript' id='janrainAuthWidget'> element. Next, a source for the script is found. In the next block, the widget checks to see if the application in which it’s embedded is currently using HTTPS or HTTP and then acts accordingly:

if (document.location.protocol === 'https:') {
    e.src = 'http://rpxnow.com/js/lib/luc/engage.js';
} else {
    e.src = 'http://widget-cdn.rpxnow.com/js/lib/luc/engage.js';
}

 
Clearly, the URL used as the source of the script is specific to the application that I created in RPX. So now our <script> tag is complete and ready to be inserted into the DOM:

var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(e, s);

 
The first line simply locates the first <script> block in the DOM. The second line ensures that our new <script> is placed before it.

And that’s it. On the basis of this self-executing widget, the DOM will now be populated with an application-specific engage.js file (itself dependency-free, unless the customer wishes otherwise) that will be loaded in an asynchronous, non-blocking fashion, which means that other JavaScript that needs to be loaded won’t compete with it and can load at the same time.

Going dependency-free: not for everyone

This is a pretty basic example that involves only a small handful of functionality available through the DOM API, but I hope that it shows that dependency-free JavaScript really isn’t as scary as you might think.

As I said before, I’m not necessarily arguing for a “return to roots”-style movement in the JavaScript community, in which we all toss aside all the frameworks we’ve been using these past few glorious, framework-enabled years. I do think, though, that dependency-free JavaScript is very much worthy of consideration in a larger variety of cases than you may think.

Our Janrain Engage widgets are textbook use cases for ditching frameworks and getting back in touch with the DOM API, as motley and inelegant as it may sometimes be. We think that developers that want to use Janrain have enough to worry about; there’s no reason to make them worry about delicately tiptoeing around our widgets.

So if you think that you might have a good use case for weaning yourself off of JS dependencies, give it a shot. There may be less to lose than you’ve been brought to think.