Support Asynchronous Module Loading (require.js) #57

Closed
aiwilliams opened this Issue Dec 21, 2011 · 16 comments

Comments

Projects
None yet
8 participants

This is becoming more and more an accepted practice. jQuery 1.7 and even jQuery Mobile (in a branch) are supporting Asynchronous Module Loading. I have modified a copy of backbone-relational.js to employ a method which @jrburke implements here: https://github.com/jrburke/backbone/tree/optamd3. For this project:

(function(root, factory) {
  // Set up appropriately for the environment.
  if (typeof exports !== 'undefined') {
    // Node/CommonJS
    factory(root, exports, require('underscore'), require('backbone'));
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define('backbone-relational', ['underscore', 'backbone', 'exports'], function(_, Backbone, exports) {
      factory(root, exports, _, Backbone);
    });
  } else {
    // Browser globals
    factory(root, {}, root._, root.Backbone);
  }
}(this, function(root, exports, _, Backbone) {
  // Backbone.Relational extensions to Backbone
  // NOTE: the few lines assigning the exports, _, and Backbone are no longer needed
}));

I have not tested this in Node, nor without Require.js.

jrburke commented Dec 21, 2011

I'm happy to help with this if there are questions. In particular, there are some notes on updating existing libraries, and the guidance for these higher level libraries is to use an anonymous define call. Also, since this library does not need root/window global (all that is done in this registration block), then the code can be reduced to:

(function(root, factory) {
  // Set up appropriately for the environment.
  if (typeof exports !== 'undefined') {
    // Node/CommonJS
    factory(exports, require('underscore'), require('backbone'));
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define(['exports', 'underscore', 'backbone'], factory);
  } else {
    // Browser globals
    factory({}, root._, root.Backbone);
  }
}(this, function(exports, _, Backbone) {
  // Backbone.Relational extensions to Backbone
  // NOTE: the few lines assigning the exports, _, and Backbone are no longer needed
}));
Owner

PaulUithol commented Jan 6, 2012

Wow, that piece of code looks seriously arcane, takes a few moments to wrap your head around ;). Relational does need access to window though when in a browser context - and otherwise, at least to the scope on which a user defines his own models/collections. The code also passing root would accomplish that I think?

I have managed to avoid window so far, but it is requiring me to define models in an ordered fashion such that I can pass them by reference to the association definitions. Do you think that Relational could be refactored to look in exports for the related model types?

Owner

PaulUithol commented Jan 6, 2012

Doesn't it already do that? First, exports is assigned to module.exports:

if ( typeof window === 'undefined' ) {
    _ = require( 'underscore' );
    Backbone = require( 'backbone' );
    exports = module.exports = Backbone;
}

Then it attempts to find models (if their name is given as a string) on the exports var (which is currently either module.exports or window):

    /**
     * Find a type on the global object by name. Splits name on dots.
     * @param {string} name
     */
    getObjectByName: function( name ) {
        var type = _.reduce( name.split( '.' ), function( memo, val ) {
            return memo[ val ];
        }, exports);
        return type !== exports ? type: null;
    },

jrburke commented Jan 6, 2012

The code also passing root would accomplish that I think?

Yes, root in the snipped I posted is "window" in a browser context.

A side note on this line from your latest code post:

exports = module.exports = Backbone;

Doing exports = in a CommonJS (even node) environment will not reflect the assigned value to outside modules, it is an object only meant to be used for attaching exported properties. This might not be a big deal for this code since the var exports is a local variable, not using what might be a CommonJS-seeded value. But just mentioning in case you were wanting to use a CommonJS-seeded exports at some point.

There is an order's error in the module dependency array ( jrburke first comment):

define(['underscore', 'backbone', 'exports'], factory);

Should be:

define(['exports', 'underscore', 'backbone'], factory);

jrburke commented Mar 8, 2012

@carlosjavier84 ah yes! Thanks for the catch. I have edited my comment above to reflect your comment.

This isn't an issue per se. I've built a project with requirejs and backbone-relational and i've run into an annoying issue i've had to poorly hack around. When defining the models relations, more specifically reverse relations collection, you'll run into circular dependencies that requirejs doesn't like.

define('models/user', function(require) {
    var Backbone = require('backbone');

    return Backbone.RelationalModel.extend({});
});

define('collections/users', function(require) {
    var Backbone = require('backbone');

    return Backbone.Collection.extend({
        model: require('models/user')
    });
});

define('models/todo', function(require) {
    var Backbone = require('backbone');

    return Backbone.RelationalModel.extend({
        relations: [{
            type: Backbone.HasOne,
            key: 'user',
            relatedModel: require('models/user'),
            reverseRelation: {
                key: 'todos',
                collectionType: require('collections/todos') // Circular
            }
        }]
    });
});

define('collections/todos', function(require) {
    var Backbone = require('backbone');

    return Backbone.Collection.extend({
        model: require('models/todo') // Circular
    });    
});

requirejs can make circular dependencies immediately available through exports but that doesn't play nice with backbone's factory functions for models and collections. @jrburke correct me if I'm wrong.

I've come up with a workaround for now. That is to simply not use HasMany reverse relations when using requirejs modules.

Owner

PaulUithol commented Mar 19, 2012

Sorry for leaving this unattended, but unfortunately I don't think it's worth it to add this to Backbone-relational at the moment, seeing the stance of Underscore and Backbone on this issue. If both implemented this, something would be gained by adding it here as well; as it is, that's not so much the case..

@amccloud apparently, trying to find an object on exports in getObjectByName doesn't cut it currently? Do you have ideas on how this could be made to work better? For example, would it be possible to smooth this by letting you define the scope(s) where models/collections can be found yourself, something like Backbone.Store.setScope/addScope/removeScope? Or is the define syntax the only accepted way to expose objects to each other?

jrburke commented Mar 26, 2012

What might be nice is if collectionType could accept a function that is called when an instance is actually created, so for @amccloud's example above:

collectionType: function () {
    return require('collections/todos');
}

caveat: I'm just going off a hazy memory of the problem and the code above, @amccloud may be able to test to confirm.

ginkel commented May 25, 2012

I understand that integration of this is not going to happen in the foreseeable future, but if someone has a patch to enable method support for the model references, I'd be interested...

Owner

PaulUithol commented May 25, 2012

Please see #127. If you have more experience with these environments than me, that would probably help a lot ;).

PaulUithol reopened this Jun 19, 2012

"exports = module.exports = Backbone;" is not compatible with require.js
Uncaught ReferenceError: module is not defined

Collaborator

DouweM commented Dec 20, 2012

Superseded by #215.

DouweM closed this Dec 20, 2012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment