Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Relative paths with RequireJS modules/packages

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?

like image 301
Justin Warkentin Avatar asked Feb 15 '12 22:02

Justin Warkentin


People also ask

What is a RequireJS module?

RequireJS loads each dependency as a script tag, using head. appendChild(). RequireJS waits for all dependencies to load, figures out the right order in which to call the functions that define the modules, then calls the module definition functions once the dependencies for those functions have been called.

What is the main purpose of the RequireJS framework?

RequireJS is a JavaScript file and module loader. It improves perceived page load times because it allows JavaScript to load in the background. In particular, it enables asynchronous JavaScript loading.

What is Shim RequireJS?

As per RequireJS API documentation, shim lets you. Configure the dependencies, exports, and custom initialization for older, traditional "browser globals" scripts that do not use define() to declare the dependencies and set a module value. - Configuring dependencies.

Should I use RequireJS?

Generally you only use RequireJS in its loading form during development. Once the site is done and ready for deployment, you minify the code. The advantage here is RequireJS knows exactly what your dependencies are, and thus can easily minify the code in the correct order.


3 Answers

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

like image 55
Simon Goller Avatar answered Sep 25 '22 02:09

Simon Goller


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

like image 44
Justin Warkentin Avatar answered Sep 24 '22 02:09

Justin Warkentin


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
            });
        });
    };
});
like image 39
Paul Grime Avatar answered Sep 24 '22 02:09

Paul Grime