28

I'm fairly new to RequireJS and I've run into a bit of a problem. I've written a little framework built on Backbone using RequireJS and I want it to be re-usable in different projects. So, with some searching I learned that require allows packages. This seemed like what I was looking for. I have a main.js file to launch my app that essentially looks like this:

require.config({
  packages: ['framework']
});

require(['framework'], function(framework) {
  framework.createDash();
});

Then in the same directory as my main.js I have another directory called "framework" which contains another main.js which looks like this:

define(function(require, exports, module) {
  exports.createDash = function(dash, element) {
    require(['dash/dash.model', 'dash/dash.view'], function(DashModel, DashView) {
      return new DashView({
        model: new DashModel(dash),
        el: element ? element : window
      });
    });
  };
});

In searching I found this page which indicates that the 'require' argument should be scoped to the submodule. However, when I try to require things they are still relative to my original main.js. I've tried a number of things and searched for hours to no avail. Is there any way I can have my require/define calls within my package included relative to the main.js in it's root?

Justin Warkentin
  • 9,856
  • 4
  • 35
  • 35

4 Answers4

51

You need to define your submodule as package in the require configuration:

require.config({
  packages: [
    { name: 'packagename',
      location: 'path/to/your/package/root',  // default 'packagename'
      main: 'scriptfileToLoad'                // default 'main' 
    }]
  ... some other stuff ...
});

To load your module you just need to use your 'packagename' at the requirements:

define(['jquery', 'packagename'], function($, MyPackage) {
  MyPackage.useIt()
});

In your package you must use the ./ prefix to load your files relative to your submodule:

define(['globalDependency', './myLocalFile'], function(Asdf, LocalFile) {
  LocalFile.finallyLoaded();
});

There is a useful shortcut: If your package name equals to your location and your main file is called 'main.js', then you can replace this

  packages: [
    { name: 'packagename',
      location: 'packagename',
      main: 'main'
    }]

to this:

  packages: ['packagename']

As far as I can see, you already tried to define a package but did you also use the ./ prefix? Without this prefix require will try to find the files in it's global root-path. And without a package, ./ will be useless because the relative path is the same as the global root-path.


Cheers

Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
Simon Goller
  • 626
  • 6
  • 3
  • 3
    Does this still allow the package to make it's own separate call to require.config() and define it's own list of path aliases, so long as the files the aliases reference start with './'? I don't want to have to use the full relative path to all of the files, every time I reference them with a define(). I've since restructured the entire project, but I'm still curious. – Justin Warkentin May 27 '12 at 21:44
  • I know this answer is a bit dated, but lately, you can safely use relative paths to require modules even without packages. –  Oct 20 '13 at 07:25
5

I figured out the answer to my question, and the solution (they were not the same apparently). I guess I'll post it here in case it can help someone else in the future.

Essentially what I was wanting was to load my framework within its own context. I found the context option under the configuration section on require's website and an example of how to use it. Originally I tried this by doing something like:

var req = require.config({
    baseUrl: 'framework',
    context: 'framework',

    paths: {
        jQuery: 'lib/jquery/jquery-1.7.min.js',
        Underscore: 'lib/underscore/underscore.min.js',
        Backbone: 'lib/backbone/backbone.min.js',
        etc...
    }
});

req(['main'], function() {});

There were two problems with this. First, my 'req' variable was being defined outside of the framework, but I wanted the framework to define it's own paths. And second, whenever a file outside of the framework would require a file within the framework, which would in turn require 'jQuery', for example, then jQuery (or whatever else) wouldn't be required from within the context of the framework instance of require and so it couldn't find the file.

What I ended up doing was defining my framework's main.js to look something like this:

var paths = {
    jQuery: 'lib/jquery/jquery-1.7.min.js',
    Underscore: 'lib/underscore/underscore.min.js',
    Backbone: 'lib/backbone/backbone.min.js',
    etc...
};

define(function() {
    var exports = {};

    exports.initialize = function(baseUrl, overridePaths, callback) {
        if(!overridePaths) {
        overridePaths = {};
        }
        if(baseUrl && baseUrl[baseUrl.length - 1] != '/') {
            baseUrl = baseUrl + '/';
        }

        var fullpaths = {};
        for(var path in paths) {
            // Don't add baseUrl to anything that looks like a full URL like 'http://...' or anything that begins with a forward slash
            if(paths[path].match(/^(?:.*:\/\/|\/)/)) {
                fullpaths[path] = paths[path];
            }
            else {
                fullpaths[path] = baseUrl + paths[path];
            }
        }

        var config = {paths: fullpaths};
        for(var pathName in overridePaths) {
            config.paths[pathName] = overridePaths[pathName];
        }
        require.config(config);

        // Do anything else you need to do such as defining more functions for exports

        if(callback) {
            callback();
        }
    }

    return exports;
});

And then in my project's main.js file I just do this:

require(['framework/main'], function(framework) {
    // NOTE: This setTimeout() call is used because, for whatever reason, if you make
    //       a 'require' call in here or in the framework without it, it will just hang
    //       and never actually go fetch the files in the browser. There's probably a
    //       better way to handle this, but I don't know what it is.
    setTimeout(function() {
        framework.initialize('framework', null, function() {
            // Do stuff here
        }
    }, 0);
});

This takes whatever is passed in to the framework's initialize() method for 'baseURL' and prepends that to any paths that the framework defines that do not start with a forward slash or 'anything://', unless they are override paths. This allows the package using the framework to override things like 'jQuery'.

Justin Warkentin
  • 9,856
  • 4
  • 35
  • 35
2

This worked for me, adding a "./" prefix to the module names:

define(function (require, exports, module) {
    exports.createDash = function (dash, element) {
        require([ './dash/dash.model', './dash/dash.view' ], function (DashModel, DashView) {
            return new DashView({
                model : new DashModel(dash),
                el : element ? element : window
            });
        });
    };
});
Paul Grime
  • 14,970
  • 4
  • 36
  • 58
  • Thanks for the response. The './' did work for loading those two components, but I guess I didn't do a great job explaining the question (mostly due to the fact that I don't think I fully understood the question at the time I posted). Really my problem came down to, how do I define paths with require.config() that can be used from both within the framework context and without, but still include the same file? There may be a better solution out there to what I'm trying to do, but at least I have something working now. Thanks again! – Justin Warkentin Feb 22 '12 at 17:58
1

A process that worked well for me for allowing a package with submodules to be used directly from data-main or from an outside framework, assuming that a main.js (or other package main) is called by a particular name, was to use var baseUrl = require.toUrl('packageName') + '/../' as a prefix to a require.config({ paths: { ... } }) configuration file. For instance:

var music21Base = require.toUrl('music21') + '/../';

require.config({ paths: {
                          'jquery': music21Base + 'ext/jquery/jquery.2.1.10.min';
                          'subModuleLoader': music21Base + 'src/subModuleLoader';
                         }  });

The setting of context: "xxx" worked fine for calling normal modules with ./modName but did not work for the paths argument for me.