Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Asynchronous Module Loading (require.js) #57

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

Support Asynchronous Module Loading (require.js) #57

aiwilliams opened this issue Dec 21, 2011 · 16 comments

Comments

@aiwilliams
Copy link

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
Copy link

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
}));

@PaulUithol
Copy link
Owner

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?

@aiwilliams
Copy link
Author

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?

@PaulUithol
Copy link
Owner

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
Copy link

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.

@carlosjavier84
Copy link

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
Copy link

jrburke commented Mar 8, 2012

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

@amccloud
Copy link

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.

@amccloud
Copy link

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

@PaulUithol
Copy link
Owner

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
Copy link

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
Copy link

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...

@PaulUithol
Copy link
Owner

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

@hacking-robot
Copy link

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

@hacking-robot
Copy link

@DouweM
Copy link
Collaborator

DouweM commented Dec 20, 2012

Superseded by #215.

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

No branches or pull requests

8 participants