36

I want to use a separate domain as a JavaScript framework and it will create a base require config which I can augment from the app.

foo.example.com
    main.js
    lib/foo-specific.js
framework.example.com
    framework.js <-- entry point
    lib/jquery.js
    lib/etc...

Optimally, I'd like to be able to require 'lib/foo-specific' and/or 'lib/jquery' and have the paths just resolve nicely, but from what I've found, there's no way to do this, unless I use a specific path key/value for every js file in the framework. At the moment, I've got a custom plugin to load the given path with a different base url (e.g. fw!lib/jquery), though if I wanted to use the text! plugin, it won't work as plugin chaining is unsupported.

See https://github.com/jpillora/js-framework for what I've currently got, and also https://github.com/jpillora/prettyprinter for a use case.

Is there a clean way to solve this ? or to achieve multiple base URLs ?

Note: I have also looked into multiple require instances, though I don't think that would work as I'd like the the app to be able to access the framework's config.

jpillora
  • 5,194
  • 2
  • 44
  • 56

4 Answers4

35

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.

Zaqwsx
  • 7,723
  • 2
  • 16
  • 10
jpillora
  • 5,194
  • 2
  • 44
  • 56
  • 1
    Hello. Thank you for your answer for configuring RequireJs and DurandalJs to use paths to remote servers. I've followed your approach and I can load the viewModel from the remote server. However, when attempting to pull in the associated view, it seems Durandal's viewEngine cannot read the contents of the remote html file. Any ideas? I get an error message stating allElements is null (allElements is parameter passed to ensureSingleElement in viewEngine.js). – Tom Schreck Mar 18 '14 at 04:28
  • Keep in mind that the data-main attribute should only be used when this is the entry point to your dependency chain, and nothing else! The expected result might deviate based on environment, browsers and other race conditions. So in other words--the only right answer is just to define the paths to your modules. – Zaqwsx Nov 26 '15 at 15:51
10

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.

Emil Lundberg
  • 7,268
  • 6
  • 37
  • 53
0

Have a look at how we have handled routing and the context in BoilerplateJS which is a reference architecture for large-scale product development. We have used the library crossroads.js to route modules and UI components. All there is to do is add the path/hash to the set of routes and it will be routed through the framework.

As an example all the paths of the UI components are added to the URL controller object.

As for accessing the framework configuration information throughout the application, configurations/settings can be attached to the global-context object which can be accessed inside the application.

hpd
  • 475
  • 1
  • 4
  • 16
  • Thanks for the response, though not the answer I was looking for. Turns out it's quite simple, see my answer. – jpillora Sep 09 '12 at 14:32
  • Hello. Thank you for your answer for configuring RequireJs and DurandalJs to use paths to remote servers. I've followed your approach and I can load the viewModel from the remote server. However, when attempting to pull in the associated view, it seems Durandal's viewEngine cannot read the contents of the remote html file. Any ideas? I get an error message stating allElements is null (allElements is parameter passed to ensureSingleElement in viewEngine.js). – Tom Schreck Mar 18 '14 at 04:27
0

requirejs multiversion

TRY this

context: A name to give to a loading context. This allows require.js to load multiple versions of modules in a page, as long as each top-level require call specifies a unique context string. To use it correctly, see the Multiversion Support section.