RequireJS: Is there a way to achieve multiple base

2019-01-21 05: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.

回答1:

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:

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.



回答2:

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.



回答3:

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.