As interesting as this problem was to solve, it seems I could have solved it easier by grabbing the latest version of underscore, which does indeed define _ on global even when loaded via Require.js. Anyway, I learned a lot in the process!
I’ve been enjoying working with Backbone.js in the past month or two, and now I’m getting into Require.js to better organize my projects. I ran into a killer problem that I know others have encountered, as there are plenty of fixes and workarounds for it around the web. But I’ll add mine to the mix, as I think it’s pretty simple and elegant, if I do say so myself.
The problem comes from the fact that Backbone relies on the Underscore library. In particular, in the current version (0.5.3) it expects that when Backbone is loaded, the _ identifier is defined in the global scope. If it is not, it calls require(‘underscore’) to load it and assigns _ to global. You can see this in the following snippet in the backbone.js file at http://documentcloud.github.com/backbone/backbone.js
[php lang=”JavaScript”] // Require Underscore, if we’re on the server, and it’s not already present.
var _ = root._;
if (!_ && (typeof require !== ‘undefined’)) _ = require(‘underscore’)._;[/php]
There are two problems here.
First is that if you load Underscore with require.js, it does not automatically create a _ variable in the global scope. Furthermore, because Underscore is not coded to be a require.js module, it does not return a reference to itself in the require callback. I’ll explain more in a second.
The next problem is that this is written for require on the server side, with node.js is synchronous, and require(‘underscore’)._ returns a reference to the Underscore library immediately, so it can be assigned to root._. But on the client side, require is asynchronous, and this will result in root._ being undefined, and you’ll get a failure with an error message about not being able to call the extend method of undefined.
So, the first thing we need to do is to load the Underscore library with require, prior to loading the Backbone library. This might look something like this:
[php lang=”JavaScript”]require(
[
“app”,
“order!underscore-min”,
“order!backbone-min”
],
function( app ){
app.init();
}
);[/php]
You might put this in a main.js file that is referenced in your data-main attribute where you load require.js. First we require our main app module which will contain our app logic, then underscore and backbone. Also note that I’ve added the order.js require plugin which forces underscore and backbone to load in the order they are called. For simplicity’s sake, I’m assuming that all the files are in the same folder so we don’t have to worry about paths. When all are loaded, the callback will fire and we can initialize our app. This is the first round try that I made, which gave me the error.
So again, the first problem is that when you load Underscore, it does not assign _ to global, which Backbone expects. The second problem is that it is not coded as a require.js module so if you were to do something like this:
[php lang=”JavaScript”]require(
[
“order!underscore-min”
],
function( underscore ){
window._ = underscore;
}
);[/php]
it would still not work. A module meant for require.js would return a reference to itself in that callback and we could assign it to global like that.
We need one more step, and that is to require Underscore again, after it’s loaded. This time we do not call require with the file name, but the module name, simply “underscore”. Since it’s already loaded, the callback will give us a reference to the library this time and we can assign it to window._.
This much is already known by many, but some of the solutions I’ve seen tend toward the overcomplex. I’ve worked on a few solutions myself, which also got pretty crazy. But I finally settled on something that is quite simple, and IMHO, pretty elegant. We create a new js file called underscore_fix.js, which looks like this:
[php lang=”JavaScript”]require(
[
“underscore”
],
function( _ ) {
window._ = _;
}
);[/php]
This gets called after we require Underscore by its file name, “underscore-min”, so it is loaded. Again, now that it’s loaded, requiring it by its module name will return a reference to the module itself. This we can assign to window._ and THEN we can load Backbone, which will work fine. The final main.js file looks like so:
[php lang=”JavaScript”]require(
[
“app”,
“order!underscore-min”,
“order!underscore_fix”,
“order!backbone-min”
],
function( app ){
app.init();
}
);[/php]
Simplest fix I’ve come across. Apparently, there is a version of Backbone in the works that addresses this problem internally. So hopefully, this post will become obsolete before too long. Any better solutions out there in the meantime? I’d love to hear them.
Hmm, it seems to work for me without the fix.. I see the global _ and Backbone even when loaded with RequireJS (as long as I use the order! plugin). But indeed they are not passed in to the callback function to require(), to do that requires more complex solution: http://backbonetutorials.com/organizing-backbone-using-modules/
This also removes the globals with ‘noConflict()’ , which I’m a bit conflicted about.. its more convenient to have them as globals, but its not as ‘pure’ as require() ing them.
Oh, Keith, just listen to yourself rolling around in the muck. May I not find myself in a position to need to refer to this post.
Yes, I know. I miss the days of ActionScript, when we didn’t have to resort to weird solutions and crazy workarounds. Hey…. wait a minute….