Optional loading of RequireJS modules

Posted on Tue 29 September 2015 in Code • Tagged with JavaScript, RequireJS, modules, Web Workers, DOM, AJAXLeave a comment

RequireJS is a module loader for JavaScript. Similar to its alternatives such as Browserify, it tries to solve an important problem on the web front(end): dividing JavaScript code into modules for better maintainability while still loading them correctly and efficiently without manual curation of the <script> tags.

Once it’s configured correctly (which can be rather non-trivial, though), modules in RequireJS are simply defined as functions that return arbitrary JavaScript objects:

define([
    'jquery',
    'lodash',

    'myapp/dep1',
    'myapp/dep2',
], function($, _, dep1, dep2) {
    // ... all of the module's code ...

    return {
        exportedSymbol1: ...,
        exportedSymbol2: ...,
    };
});

Before executing the function, RequireJS loads all the specified dependencies, repeating the process recursively and asynchronously. Return values from module functions are passed as parameters to the next module function, and thus the whole mechanism clicks, serving as a crafty workaround for the lack of proper import functionality1.

Relative failures

If, at some point in the chain, the desired module cannot be found or loaded, the entire process grinds to a halt with an error. Most of the time, this is perfectly acceptable (or even desirable) behavior, equivalent to an incorrect import statement, invalid #include directive, or similar mistake in other languages.

But there are situations when we’d like to proceed with a missing module, because the dependent code is prepared to handle it. The canonical example are Web Workers. Unlike traditional web application code, Web Worker scripts operate outside of a context of any single page, having no access to the DOM tree (because which DOM tree would it be?). Correspondingly, they have no document nor window objects in their global scope.

Unfortunately, some libraries (*cough* jQuery *cough*) require those objects as a hard (and usually implicit) dependency. This doesn’t exactly help if we’d like to use them in worker code for other features, not related to DOM. In case of jQuery, for example, it could be the API for making AJAX calls, which is still decidedly more pleasant than dealing with bare XMLHTTPRequest if we’re doing anything non-trivial.

Due to this hard dependency on DOM, however, Web Workers cannot require jQuery. No biggie, you may think: browsers supporting workers also offer an excellent, promise-based Fetch API that largely replaces the old AJAX, so we may just use it in worker code. Good thinking indeed, but it doesn’t solve the issue of sharing code between main (“UI”) part of the app and Web Workers.

Suppose you have the following dependency graph:

The common module has some logic that we’d want reused between regular <script>-based code and a Web Worker, but its dependency on jQuery makes it impossible. It would work, however, if this dependency was a soft one. If common could detect that jQuery is not available and fall back to other solutions (like the Fetch API), we would be able to require it in both execution environments.

The optional plugin

What we need, it seems, is an ability to say that some dependencies (like 'jquery') are optional. They can be loaded if they’re available but otherwise, they shouldn’t cause the whole dependency structure to crumble. RequireJS does not support this functionality by default, but it’s easy enough to add it via a plugin.

There are already several useful plugins available for RequireJS that offer some interesting features. As of this writing, however, optional module loading doesn’t seem to be among them. That’s not a big problem: rolling out our own2 plugin turns out to be relatively easy.

RequireJS plugins are themselves modules: you create them as separate JavaScript files having code wrapped in define call. They can also declare their own dependencies like any other module. The only requirement is that they export an object with certain API: at minimum, it has to include the load method. Since our optional plugin is very simple, load is in fact the only method we have to implement:

/* Skeleton of a simple RequireJS plugin module. */

define([], function() {

function load(moduleName, parentRequire, onload, config) {
    // ...
}

return {
    load: load,
};

});

As its name would hint, load carries out the actual module loading which a plugin is allowed to influence, modify, or even replace with something altogether different. In our case, we don’t want to be too invasive, but we need to detect failure in the original loading procedure and step in.

I mentioned previously that module loading is asynchronous, which JavaScript often translates to “callbacks”. Here, load receives the onload callback which we eventually need to invoke. It also get the mysterious parentRequire argument; this is simply a regular require function that’d normally be used if our plugin didn’t stand in the way.

Those two are the most important pieces of the puzzle, which overall has a pretty succinct solution:

/**
 * RequireJS plugin for optional module loading.
 */
define ([], function() {


/** Default value to return when a module failed to load. */
var DEFAULT = null;

function load(moduleName, parentRequire, onload) {
    parentRequire([moduleName], onload, function (err) {
        var failedModule = err.requireModules && requireModules[0];
        console.warn("Could not load optional module: " + failedModule);
        requirejs.undef(failedModule);

        define(failedModule, [], function() { return DEFAULT; });
        parentRequire([failedModule], onload);
    });
}

return {
    load: load,
};

});

The logic here is as follows:

  • First, try to load the module normally (via the outer parentRequire call).
  • If it succeeds, onload is called and there is nothing for us to do.
  • If it fails, we log the failedModule and cleanup some internal RequireJS state with requirejs.undef.
  • Most importantly, we define the module as a trivial shim that returns some DEFAULT (here, null).
  • As a result, when we require it again (through the inner parentRequire call), we know it’ll be loaded successfully.

Usage

Plugins in RequireJS are invoked on a per-module basis. You can specify that a certain dependency 'bar' shall be loaded through a plugin 'foo' by putting 'foo!bar' on the dependency list:

define([ 'foo!bar'], function(bar) {
    // ...
});

Both 'foo' and 'bar' represent module paths here: the first one is the path to the plugin module, while the second one is the actual dependency. In a more realistic example — like when our optional loader is involved — both of them would most likely be multi-segments paths:

define([
    'myapp/ext/require/optional!myapp/common/buttons/awesome-button',
], function(AwesomeButtonController) {
    // ...
});

As you can see, they can get pretty unreadable rather quickly. It would be better if the plugin prefix consisted of just one segment (i.e. optional!) instead. We can make that happen by adding a mapping to the RequireJS config:

requirejs.config({
    // ...
    map: {
        '*': {
            'optional': 'myapp/ext/require/optional',
        }
    }
})

With this renaming in place, the loading of non-mandatory dependencies becomes quite a bit clearer:

define([
    'optional!myapp/common/buttons/awesome-button',
], function(AwesomeButtonController) {

// ...
if (!AwesomeButtonController) {
    // ... (some work around) ...
}

});

Of course, you still need to actually code around the potential lack of an optional dependency. The if statement above is just an illustrative example; you may find it more sensible to provide some shim instead:

AwesomeButtonController = AwesomeButtonController || function() {
    // ...
};

Either way, I recommend trying to keep the size of such conditional logic to a minimum. Ideally, it should be confined to a single place, or — better yet — abstracted behind a function.


  1. An actual import statement has made it into the ES6 (ECMAScript 2015) standard but, as of this writing, no browser implements it. 

  2. Most of the code for the plugin presented here is based on this StackOverflow answer

Continue reading

Interruptible fade-out of DOM elements

Posted on Sun 30 August 2015 in Code • Tagged with HTML, JavaScript, DOM, jQuery, animation, promisesLeave a comment

Here’s a nice animated effect you may have seen in some applications, quite often games. An ephemeral UI element is displayed, either in a corner or pretty prominently, and after a short while it slowly fades out to transparency over several seconds:

"Ding" with full opacity
"Ding" after fading out

If you hover your mouse over it, though, it turns opaque right away and stays like that until you move away with the cursor. It then tries to disappear again, lest you stop it once more and restore to full opacity — and so on. Leave it for a moment, though, and it eventually vanishes.

Cool, eh? Wouldn’t it be nice to have a similar mechanism for, say, notifications displayed within a web app? Sure it would be! And it doesn’t even seem all that complicated, so let’s give it a try.

A promising API

Before delving into implementation details, it’s a good practice think for a moment about an API that we’ll expose to the user. Since it looks like a relatively simple problem, we don’t need no classes or frameworks. All that’s necessary is a simple function:

function interruptibleFadeOut(element, options) {
    // ...
}

Surely, however, whoever calls it would like to be able to tell when the animation finishes (e.g. to remove the element they’ve passed from the DOM tree). Simply waiting a predefined number of seconds is not sufficient, because — as described above — the effect could be restarted arbitrary many times through mouse interaction.

We could pass a callback, of course, but the modern JavaScript way of dealing with asynchronicity and concurrency is through promises. Following this pattern, our function shall return a promise that resolves when the effect ends. Disregarding the possible user interaction for now, a minimal implementation of the fade-out alone would simply use the jQuery.fadeOut method and look something like this:

function interruptibleFadeOut(element, options) {
    if (typeof options === 'number') {
        options = {fadeOut: options};
    }

    element = $(element);
    return new Promise(function(resolve) {
        element.fadeOut(options.fadeOut, function() {
            resolve();
        });
    });
}

The constructor of Promise is admittedly a little weird, as it employs a level of abstraction and indirection that is indicative of (more) functional programming languages. You pass it a function that’s invoked immediately with an argument that itself is a function. (Actually two arguments, but we ignore the other one here). That input function, resolve, should be called whenever the new promise “completes”, in whatever sense it’s appropriate for the particular use case.

Here, we’re resolving the promise when our animation ends. This enables callers to react to this in a very straightforward way:

var elem = $('#foo');
interruptibleFadeOut(elem, 400 /* ms */).then(function() {
    elem.remove();
});

As a small aside, the Promise class is a ECMAScript 6 feature, which means it may not be available in all the browsers. The open-source Q library is often used as a compatibility shim, among many other libraries implementing the Promises/A+ recommendation.

Interaction handling

There’s little value in just wrapping an existing jQuery method, so let’s make it more interesting through mouse events. We want hovering on the element to pause the animation, while moving the cursor away should resume it:

return new Promise(function(resolve) {
    element.on('mouseenter', function() {
        element.stop(true);  // true == discard remaining animation stages
        element.css('opacity', 1.0);
    });
    element.on('mouseleave', function() {
        element.fadeOut(options.fadeOut, function() {
            resolve();
        });
    });

    element.fadeOut(options.fadeOut, function() {
        resolve();
    });
});

That will mostly work even with the above code, but there are several problems that need to be addressed immediately:

  • The mouseenter and mouseleave event handlers are should only fire while the fade-out effect is being played. Currently, they “leak”, and remain bound even after it finishes, interfering with given DOM element for as long as it exists.
  • The whole concept of hovering is contingent upon the user having a pointing device. In today’s mobile world, this excludes a significant fraction of clients. We need an alternative interaction pattern for touchscreen devices, and one possible option is restart the effect on click/tap.
  • There is a fair amount of repetition here, and it will only get worse once the previous problems are dealt with.

Cleaning up

A correct (and cleaner) version is quite a bit longer, but shouldn’t be too hard to follow:

return new Promise(function(resolve) {
    // Event handlers used by the effect.
    var events = {
        mouseenter: function() { interruptAnimation(); },
        mouseleave: function() { delayedFadeOut(); },
        click: function() {
            interruptAnimation();
            delayedFadeOut();
        },
    };
    Object.keys(events).forEach(function(name) {
        element.on(name, events[name]);
    });

    // Helper functions.
    var interruptAnimation = function() {
        element.stop(true);
        element.css('opacity', 1.0);
    };
    var delayedFadeOut = function() {
        if (options.delay) {
            element.delay(options.delay);
        }
        element.fadeOut(options.fadeOut, function() {
            Object.keys(events).forEach(function(name) {
                element.off(name, events[name]);
            });
            resolve();
        });
    };

    // Start the animation.
    if (options.fadeIn) {
        element.fadeIn(options.fadeIn);
    }
    delayedFadeOut();
});

The main part is of course the delayedFadeOut function. It is invoked right at the beginning, as well as whenever the animation has to be started again.

We also put the event handlers in a “map” (JavaScript object). This makes it easy to initially bind them to the element, and — more importantly — unbind them when the effect ends.

As a final touch, we also allow the caller to specify how long the element should remain at its full opacity before it starts to fade out (delay), and to optionally add a fadeIn stage at the very beginning to make everything look a little nicer. (I recommend no more than 300-400ms for that part, though).

Practical example

A complete code sample for the interruptibleFadeOut function can be seen in this gist. To use it in practice, you’d most likely need at least a little bit of CSS and additional JavaScript to position the element appropriately:

<div id="toast">Hello, world!</div>
#toast {
    position: fixed;
    display: none;

    /* These are arbitrary */
    top: 100px;
    width: 80%;
    height: 50px;
}
var toast = $('#toast');
toast.offset({
    left: Math.round($(window).width() - element.outerWidth() / 2),
});
toast.show();
interruptibleFadeOut(toast, { fadeIn: 200, delay: 1000, fadeOut: 3000 })
    .then(function() { toast.remove() });

Styling it to look like a level-up ding is left as an exercise for the reader :)

Continue reading