24

I have this code which, for reasons I can't understand, produces an empty object when using require(). My file structure is like this:

src
|__ public
    |__ javascript
        |__ collections
            |   categories.js
            |   listings.js <-- Always an empty object
        |__ models
            |   category.js
            |   employer.js
            |   listing.js
            |   location.js
        |   routing
        |   templates
        |   tests
        |   ui-components
        |   views

The problem file is collections/listings.js, which seems to simply output as an empty object when required like so:

var ListingsCollection = require('../collections/listings')

src/public/javascript/collections/listings.js looks like this:

var $        = require('jquery'),
    _        = require('underscore'),
    Backbone = require('backbone'),
    Listing  = require('../models/listing');

Backbone.$ = $;

module.exports = Backbone.Collection.extend({
    url: '/listings',

    model: Listing,

    parse: function (response) {
        return response.listings;
    }
});

Here is an example of where things go wrong:

var $                  = require('jquery'),
    _                  = require('underscore'),
    Backbone           = require('backbone'),
    LocationModel      = require('../models/location'),
    ListingsCollection = require('../collections/listings');

Backbone.$ = $;

console.log(ListingsCollection); // > Object {}

module.exports = Backbone.Model.extend({

    urlRoot: '/employers',

    model: {
        location: LocationModel,
        listings: ListingsCollection
    },

    parse: function (response) {
        var employer = response.employer;

        // Create the child listings
        employer.listings = new ListingsCollection;

        return employer;
    },

    toJSON : function () {
        var json = _.clone(this.attributes);

        _.each(_.keys(this.model), function (child) {
            if (this.get(child)) {
                json[child] = this.get(child).toJSON();
            }
        }.bind(this));

        return json;
    }
});

So there it is - That collection never requires into the employer model such that it can be used to create a child collection for the parent model. I've looked at the source and researched this issue but I'm coming up with nothing so far... It's perplexing.

11
  • Are there any errors in the console? Commented Oct 19, 2014 at 0:39
  • does it work with .js at the end? Commented Oct 19, 2014 at 0:45
  • @machineghost - The console errors occur when I attempt to use the empty object as the expected Collection object. The browserify bundling process runs without a hitch. Commented Oct 19, 2014 at 0:48
  • 3
    requirejs.org/docs/api.html#circular Commented Oct 19, 2014 at 2:54
  • 2
    It's just part of the "fun" of Require: you can't have module A that imports B that imports A (or B that imports C that imports A, or ...). Hopefully it won't require to much modification though; that link I posted has some tips. Commented Oct 20, 2014 at 16:07

1 Answer 1

22

Since my comment seems to have answered this issue, I thought I'd write it up formally.

When using Require.js you have to think about dependencies between modules. In some sense, this is the same issue as if you weren't using require. Let's pretend you have two files, A.js and B.js, which define an "A" function and a "B" function respectively:

// A.js
window.A = function() {
    // ...
};

// B.js
window.B = function() {
    // ...
};

You can add those files in any order to your page, and your code will work. But what if your definition of "B" depends on the definition of "A":

// B.js
window.B = window.A || function() {
    // ...
};

Now, suddenly order matters: you have to include your B.js file after your A.js file or else B's code won't work. And if your A.js also depends on your B.js ...

// A.js
window.A = window.B || function() {
    // ...
};

Then your code is fatally flawed, because B depends on A and A depends on B, and one has to be defined first. This is what is known as a "circular dependency".

Require has the same problem, only it's easier to miss because Require abstracts a lot of things from you. At the end of the day though Require (being JavaScript code) has to run sequentially, which means it has to define your modules in some order. If your module A depends on module B, and B depends on A, you'll have the same circular dependency issue.

Similarly if you have A that depends on B, and B that depends on C, and C that depends on A, you'll also have a circular dependency. Or if you have a C that depends on D that depends ... well you get the idea. Ultimately any module that depends on any other module has to ensure that neither the dependent module or any of its dependencies depend on the original module.

So how do you fix your code? The obvious way would be to remove the circular dependencies, which will certainly work, but there's also another option. Let's say your A depends on B, but only at run-time, not at load time. In other words instead of:

// A.js
define(['B'], function(B) {
    return B || function() {
        // ...
    };
});

you have:

// A.js
define(['B'], function(B) {
    return function() {
        B();
    };
});

In this case you can use the single argument "synchronous" form of require to avoid having to require "B" at the top of your file:

// A.js
define([], function() {
    return function() {
        var B = require('B');
        B();
    };
});

Because we only use B after we've defined all our modules Require doesn't have to worry about A coming after B; it can define it whenever it wants because, by the time you actually want to use B, it will already be defined. Of course, this assumes that you have some module which actually does include "B" at the top (if you didn't require wouldn't even know that B exists).

Hope that helps.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks machineghost. You stopped me from going down a rabbit hole when I asked this question and I ended up rethinking the structure of my code to avoid this issue instead. Needless to say it saved a lot of time.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.