RequireJS: Is there a way to achieve multiple base URLs?

The problem

I had a similar problem while trying to set up a testing environment. I had a file structure like this:

myApp/
    src/
        js/
            app.js
            data.js
            lib/underscore.js
    test/
        karma.conf.js
        test-main.js
        matchers.js
        spec/
            data.js

Here's where it gets tricky: my app scripts (app.js and data.js) assume a RequireJS configuration that resolves data to src/js/data.js, lib/underscore to src/js/lib/underscore.js etc, so I need that configuration in my test environment as well:

test/test-main.js
-----------------
require.config({
  // Karma serves files under /base, which is the basePath from your config file
  baseUrl: '/base/src/js',
  // ...
});

Now I can write my tests:

test/spec/data.js
-----------------
define(['data', '../../test/matchers'], function(dataModule) {
    describe('The data module', function() {
        it('should satisfy my custom matcher', function() {
            expect(dataModule).toSatisfyMyCustomMatcher();
        });
    });
});

With some custom matchers:

test/matchers.js
----------------
define([], function() {
    beforeEach(function() {
        this.addMatchers({
            toSatisfyMyCustomMatcher: function() {
                return this.actual.isGood;
            },
        });
    });
});

However, that '../../test/matchers' part is horrendously ugly. The test specifications shouldn't be bothered with knowing file paths to other modules - that's RequireJS's job. Instead we want to use symbolic names.

The solution

The RequireJS paths config can also map directories.

The path that is used for a module name should not include an extension, since the path mapping could be for a directory.

So, the solution is a simple path config:

test/test-main.js
-----------------
require.config({
  baseUrl: '/base/src/js',
  paths: {
      test: '../../test',
  },
  // ...
});

Now I can refer to the test directory as if it were a child of the baseUrl:

test/spec/data.js
-----------------
define(['data', 'test/matchers'], function(dataModule) {
    // ...
});

Which in my case effectively comes out pretty much the same as if I could have multiple baseUrls.


Answered by James Burke on RequireJS Github Issue's page: Issue #447: Multiple Base URLs · jrburke/requirejs.

Turns out to be quite simple if data-main is the only entry point to your scripts(comments for more info), I solved my particular problem with the following:

My app's index.html:

<script src="http://framework.jpillora.com/js/lib/require.js" 
  data-main="http://framework.jpillora.com/js/framework" > </script>

has the requirejs entry point set to framework.js:

var framework = ... //set using script elements src attribute

require.config({

    baseUrl: 'js/',

    //Framework paths
    paths: {
      'framework': framework,
      'lib'      : framework + 'js/lib',
      'ext'      : framework + 'js/ext',
      'util'     : framework + 'js/util'
    },

    //Shortcuts
    map: {
      '*': {
        ...
      }
    },

    //Non-modularised libraries with deps
    shim: {
      ...
    }
});

require(['main']);

So instead of normally doing index.html->main.js, we're adding an extra step index.html->framework.js->main.js, which gives the app code knowledge of paths to the framework code.

For example, in the app http://prettyprint.jpillora.com/, once require has loaded framework.js, it will setup paths to lib/... which to http://framework.jpillora.com/ and set the baseUrl as ./js/ so once main is required, it will have the base url set to it's own domain and lib pointing to another domain.

Which results in require(['lib/foo', 'view/bar']); resolving to:

http://framework.jpillora.com/js/lib/foo.js and http://prettyprint.jpillora.com/js/view/bar.js

As displayed here, the app is only a main.js everything else comes from the framework:

chrome devtools loaded app

So finally, whenever I load an app's main.js via with the above framework.js, I then have access to all of my commonly used libraries and utility classes. See app source.

Also note, with the r.js optimiser and a nice local file structure, one can also optimise the app into a single js file pulling only what's required from framework.