Thursday, January 15, 2015

Why I have stopped using requirejs (and you should too)

I have used requirejs extensively and I have written many posts about it. I think it is very ingenious and well designed.
It tries to solve more than one problem at the same time (in a very elegant way) but nowadays these problems are not so important and they have better solutions.

Loading scripts asynchronously

This was one of the main selling point of requirejs in the past. Now it is not necessary anymore. It is much better moving (back) the script tags on the top and use the async attribute as described by this great article. The async attribute now is very well supported !

Loading dependencies

Requirejs can dinamically load dependencies when they are required. But often you want to have the control. Sometime is better to include a library when you load the page (bundling more than a library together, for having them saved in the cache) and sometime you want to load it on demand. In case it is very easy do something like:

var script = document.createElement('script');
script.src = "http://www.example.com/script.js";
document.head.appendChild(script);

Isolate dependencies

Requirejs is even able to run 2 different versions of the same library. But is a feature rarely used and to be honest in 99.9 % of the cases using the module pattern is more than enough.

The only issue

The only issue of having all these asynchronous bundles (using the async attribute) is managing the execution order. You can use a tiny library like this one:

(function (w){

var go = {}, wait = {};
w.later = function (dep, func){
    if (go[dep]) func();
    else {
        wait[dep] = wait[dep] || [];
        wait[dep].push(func);
    }
}

w.later.go = function (dep){
    var funcs =  wait[dep] || [], l = funcs.length;
    delete wait[dep];
    go[dep] = true;
    for (var i = 0; i < l; i++){
        try{
            funcs[i]();
        }
        catch (e){
            console && console.error(e);
        }
    }
}
}(window));

This could be the only JS to be loaded synchronously. For maximum performances you can also minify it and inline in the HTML.
Then you can manage the dependencies at execution time:

later('foo', function (){
   // waiting for the bundle named foo (it is an arbitrary string)
});

You only need to put this instruction at the end of the bundle "foo":

later.go("foo");

There are still valid use cases for requirejs but I suggest to keep your build process lean, tweak performances by hand, use the async attribute and the module pattern.
Simpler and more performant !

Edited: and what about "defer"?

This article suggests to use async and defer together for improving performances on older browsers. I suggest to not do that, unless you know what your script is doing. This is because of this bug. The bug is even worst of what it seems, if you inject a script tag inside a DEFERred script the execution will stop waiting for the injected script to be downloaded and executed. So be careful!