29

In my backbone app, I need to provide a fallback for each required file, in the case that the CDN that delivers them fails.

I have tried overwriting require.onError like so:

require.onError = function (err) {
    if (err.requireType === 'timeout') {
        var url = err.requireModules;

        if (!!~url.indexOf("jquery/"))
            console.warn("CDN timed out, falling back to local jQuery.js")
            require(["libs/jquery"]);
            return;
        if (!!~url.indexOf("jqueryui/"))
            console.warn("CDN timed out, falling back to local jQueryUI.js")
            require(["libs/jqueryui"]);
            return;
        if (!!~url.indexOf("underscore"))
            console.warn("CDN timed out, falling back to local underscore.js")
            require(["libs/underscore"]);
            return;
        if (!!~url.indexOf("backbone"))
            console.warn("CDN timed out, falling back to local backbone.js")
            require(["libs/backbone"]);
            return;
    }
}

The problem is that this will asynchronously load the fallback files. I need these files to load in order, just like the original require statement, where I use the order! plugin.

With the overridden onError: when the CDN fails to load, the fallback load is started, but not waited for. This presents a problem because the scripts are ordered to be loaded based on their dependencies. Here is a look at my original require statement, that depends on the CDN:

require([
    "order!http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js",
    "order!http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js",
    "order!http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js",
    "order!http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js",
    "order!utils/date",
    "order!core/core",
    "order!core/errors",
    "order!core/constants"
], function() {
    ...
}
5
  • Both CDN's are not terrible, they only fail on average once a day for a window of about a minute at most, and are free. Regardless, I feel this is an interesting problem to solve. Always good to have fallbacks even if you have a perfect CDN, to preserve user experience. Can you suggest any other CDNs?
    – Austin
    Aug 22, 2012 at 13:06
  • I understand where you are coming from, but I am not in the position to suggest purchase of anything like this for our company without providing ample proof that I have tried everything to make the current system work. Also, I am interested in any programming feat, and I can see this particular solution being useful to many people, as many developers (freelancers in particular) do not use pay-for CDNs.
    – Austin
    Aug 22, 2012 at 13:13
  • 1
    CDN is real cheap now ($2 to $3 per month) ... I am using AccuWebHosting.Com CDN and never experienced any downtime. Well, if one server fails in CDN, the content is immediately delivered from the closest available next POP ... And there are usually 40 to 50 POPs in a good CDN ... Aug 22, 2012 at 13:29
  • 12
    I don't understand the consternation over this question. Popular CDN + fallback is a common scenario, and gives the advantage that many users won't even have to download the JS as it's already cached in their browser. Regardless, other async loaders DO solve this real world problem. The OP was just asking how to solve it with requirejs. Aug 22, 2012 at 14:04
  • @numbers1311407, this is my stance exactly.
    – Austin
    Aug 22, 2012 at 14:06

2 Answers 2

58

What version of RequireJS are you using? It seems you might want to configure jQuery's fallback as a path in your config, then set up jQuery as a dependency on the other modules that need it. If you're using ~> 2.0, something like (untested):

// in your requirejs config
requirejs.config({
    //To get timely, correct error triggers in IE, force a define/shim exports 
    // check.
    enforceDefine: true,
    paths: {
        jquery: [
            'http://somecdn.com/jquery.min', // your cdn
            'lib/jquery' // your fallback
        ],
        jqueryui: "http://somecdn.com/jquery-ui.min.js"
    },
    shim: {
      jqueryui: ['jquery']
    }
});

// then in your requires
require([jquery, jqueryui, foo, bar], function($) {
    // stuff
});

They talk about how to do it in the wiki. If you are not using v2.x, there is a method for handling that here too.

If all the modules are configured to specify their own dependencies, you shouldn't need to worry about the order! directives either.

8
  • Beat me to the post by 2 minutes, but yes, this is the answer I went with.
    – Austin
    Aug 22, 2012 at 14:26
  • Haha not even. I think it was about 15 seconds. Aug 22, 2012 at 14:27
  • 2
    How is this managed once you run the optimiser? Does it add script tags to the html for excluded files? Mar 1, 2013 at 16:42
  • 2
    @sidonaldson The fallback path exists to deal with potentially unavailable external resources, and AFAIK, according to the docs, the optimizer only works with local resources. I believe if you wanted to bundle the external resource, you'd download it first. Anyone correct me if I'm wrong please; I've always preferred solutions like browserify to AMD. Jul 30, 2013 at 21:53
  • Have you tested this ? For my config, if the paths are of the form [CDN, local], and the CDN script is not found (Wi-Fi turned off), then those whose shim.deps refer to the script seem to run before the local fallback is chosen.: requirejs.config({ baseUrl: 'bower_components', paths: { /* JS */ 'jquery': ['//code.jquery.com/jquery-1.10.2.min', 'jquery/jquery.min'], 'bootstrap': 'bootstrap/dist/js/bootstrap.min' }, shim: { 'bootstrap': { deps: ['jquery'] } } } }); gives Uncaught Error: Bootstrap requires jQuery Dec 29, 2013 at 3:41
20

I have found a solution to the problem provided in RequireJS 2.x.x. There was a demand for this solution, so in turn, RequireJS added a paths object to their config. This provides fallback functionality for CDNs, should they fail.

It should also be noted that the order! plugin has been deprecated in Require 2.0, so I also needed to make use of the shim object to define dependencies. It's actually a pretty interesting idea.

Here is my new require.config:

require.config({
    urlArgs: "ts="+new Date().getTime(), // disable caching - remove in production
    paths: {
        jquery: [
            "http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min",
            "libs/jquery"
        ],
        jqueryui: [
            "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min",
            "libs/jqueryui"
        ],
        underscore: [
            "http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min",
            "libs/underscore"
        ],
        backbone: [
            "http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min",
            "libs/backbone"
        ]
    },
    shim: {
        'jqueryui': ['jquery'],
        'underscore': ['jquery'],
        'backbone': ['underscore'],
        'core/core': ['underscore'],
        'core/errors': ['core/core'],
        'core/constants': ['core/core']
    }
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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