Telerik blogs

Recently I was chatting with Derick Bailey (he's working on some very slick Kendo UI tools - so keep your eyes peeled for that). Derick was interested in using Icenium Everlive's client JavaScript library in a project that also uses RequireJS. Being familiar with RequireJS, I jumped in to help Derick configure his application - since RequireJS provides a really slick "shim" feature that allows traditional JavaScript libraries (i.e. - not AMD or CommonJS modules) to be used inside an AMD-based project. As we worked together, we discovered some important bits of information on how to best configure the two to work together.

TL;DR - if you just want the configuration options on using RequireJS and Everlive together, jump down to "The Fix". If you'd like to understand why these configuration options are important (it's worth knowing!), read on.

Wait - Back Up To The Part About Modules

No, don't worry. We're not going into the depths of the revealing module pattern, or CommonJS modules or AMD. But it's import to understand what an AMD wrapper looks like, so let's check one out.

reqwest

Everlive has three dependencies:

reqwest is an HTTP utility library that includes support for XHR, JSONP & CORS. When you view the reqwest source, you'll see this wrapper around the library:

!function (name, context, definition) {
  if (typeof module != 'undefined' && module.exports) module.exports = definition()
  else if (typeof define == 'function' && define.amd) define(definition)
  else context[name] = definition()
}('reqwest', this, function () {

    // module code here

    return reqwest
});

This wrapper is one possible implementation of what's referred to as a Universal Module Definition. The goal of a UMD is to provide a single JavaScript library that can play nicely in CommonJS, AMD and 'plain' Javascript environments. This entire wrapper is an immediately invoked function expression (IIFE). You can see that the name argument that's passed in is "reqwest", the context argument is whatever this is at the time the wrapper is executed (so, the window object, if this is run in a browser) and the definition argument is a function that returns the actual instance of the library. The real interesting stuff, though, is the if statement inside the IIFE.

  • The first part of the if statement – if (typeof module != 'undefined' && module.exports) module.exports = definition() – detects for a CommonJS environment, and if detected, assigns the module's definition function output to module.exports.
  • The first else of the if statement – else if (typeof define == 'function' && define.amd) define(definition) – detects for an AMD environment, and if detected, calls define and passes in the module definition function.
  • The last else of the if statement simply places the result of the module definition function on the context (which is the value of this when the IIFE executes - the window if we're in the browser).

Why am I explaining this - and what does it have to do with using Everlive?!

If you look closely at the entire if statement, you'll notice that the only time the reqwest module gets placed on the global object is if neither CommonJS nor AMD are detected.

Got that? Good - let's put a bookmark there and come back to it in a second.

RequireJS and non-AMD libraries

Since RequireJS 2.0, it's been possible to shim "traditional browser global" libraries so that you don't have wrap your favorite third party library with your own AMD wrapper before you can use them in your project. Let's see how this works:

requirejs.config({
  paths: {
    kendo                 : "vendor/kendo.all.min",
    underscore            : "vendor/everlive/min/underscore.min",
    rsvp                  : "vendor/everlive/min/rsvp.min",
    reqwest               : "vendor/everlive/min/reqwest.min",
    "kendo.data.everlive" : "vendor/everlive/min/kendo.data.everlive.min",
    Everlive              : "vendor/everlive/min/everlive.min"
  },
  shim: {
    "underscore": {
      exports: "_"
    },
    "Everlive": {
      deps: ["underscore", "rsvp", "reqwest"],
      exports: "Everlive",
    }
    "kendo.data.everlive": {
      deps: ["Everlive"]
    }
  }
});

 

In a nutshell, the above require.config call is configuring RequireJS in our application. The paths member maps the path to a library to the name/id by which we want to refer to it. This makes it easier for us, since typing kendo sure beats typing vendor/kendo.all.min.

The shim member allows us to tell RequireJS that we have a library that isn't an AMD lib, and how to get at the module value. For example, note the first member under shim: underscore. The exports property under underscore has a value of "_" - this tells RequireJS that the module value for underscore is the "_" member of the global object (the window, if you're in the client). From now on, if any AMD module takes a dependency on underscore (which is not an AMD lib by default), RequireJS knows to pass in window._ as the module value.

Meanwhile, Back at the Everlive Ranch

So, what happens in the above scenario when we have the following factors at play:

  • RequireJS is present
  • Everlive - currently a traditional js module (no AMD/CommonJS/UMD wrapper)
  • reqwest - wrapped in a UMD, and does not set the window.reqwest member if an AMD loader is present

Any guesses?

Here's what happens: reqwest, while available to any AMD module that requires it, will not exist on the window. However, Everlive depends on it being available on the window. This is a recipe for a long & tedious crawl through the stack trace, which leads to hate…which leads to suffering…and we all know that suffering leads to bad acting.

Thankfully, we're here to save you the trouble.

It's important to note that traditional JavaScript apps (non-AMD, non-CommonJS) will not run into this problem, since reqwest only avoids putting it's reference on the window when AMD or CommonJS environments are present.

The Fix

You have two options to mitigate this issue at the moment:

1.) Add an init method to the shim

requirejs.config({
  paths: {
    kendo                 : "vendor/kendo.all.min",
    underscore            : "vendor/everlive/min/underscore.min",
    rsvp                  : "vendor/everlive/min/rsvp.min",
    reqwest               : "vendor/everlive/min/reqwest.min",
    "kendo.data.everlive" : "vendor/everlive/min/kendo.data.everlive.min",
    Everlive              : "vendor/everlive/min/everlive.min"
  },
  shim: {
    "underscore": {
      exports: "_"
    },
    "Everlive": {
      deps: ["underscore", "rsvp", "reqwest"],
      exports: "Everlive",
      init: function(underscore, rsvp, reqwest){
        this.reqwest = reqwest;
        return this.Everlive;
      }
    }
    "kendo.data.everlive": {
      deps: ["Everlive"]
    }
  }
});

This fix involves a slight hack, by adding an init method to our Everlive shim. Inside the init method, we simply place reqwest on the window (since the dependencies of Everlive are passed into the init method).

2.) Place reqwest on the window before Everlive is required

This means that if we normally start our app like this:

// Start the main app logic.
requirejs(["app"], function(app) {
    app.init();
});

Then, we alter it to look like this:

// Start the main app logic.
requirejs(["app", "reqwest"], function(app, reqwest) {
    window.reqwest = reqwest;
    app.init();
});

Either approach is effectively doing the same thing: making reqwest available on the window for any non-AMD libraries that depend on it. Just choose the hack that fits your mood.


About the Author

Jim Cowart

Jim Cowart is an architect, developer, open source author, and overall web/hybrid mobile development geek. He is an active speaker and writer, with a passion for elevating developer knowledge of patterns and helpful frameworks. 

Comments

Comments are disabled in preview mode.