Document how to define a require-config file shared across different scripts #354

Closed
idflood opened this Issue Jun 28, 2012 · 32 comments

Projects

None yet
@idflood
idflood commented Jun 28, 2012

Hi,

The new version of requirejs looks really promising but I have a little issue. Previously I used the !order plugin to load a main config file which was used for the normal page, tests and other places.

For instance, the file was loaded from the boot.js file : https://github.com/idflood/next-boilerplate.js/blob/master/src/scripts/boot.js
And the config file looked like this: https://github.com/idflood/next-boilerplate.js/blob/master/src/scripts/require-config.js

Now, without the !order plugin this trick doesn't work anymore. I have some ideas about possible solutions but I can't find an elegant one. One easy solution would be to have nested require calls, but if I'm right the r.js optimizer will not really work with that.

Any idea to solve this in an elegant way?

@millermedeiros
Member

There are many ways to solve the problem:

  1. put the config on the HTML before you require the top-level module.
  2. load the config.js with another <script> tag.
  3. do a nested require() on the HTML file (require config than require your main).
  4. do a nested require() inside main.js.
  5. keep the configuration inside main.js

The best approach will vary based on your project structure, I've been doing 5 way more often than the others since I usually have a single entry-point for all pages, but in some cases I used 1 and 2.

Documentation is indeed needed since that is a recurrent question.

@idflood
idflood commented Jun 28, 2012

Thanks for this answer.

The problem with point 3 and 4 is that r.js will not optimize the nested dependencies by default, only if findNestedDependencies is set to true. I prefer to keep this setting to false so it doesn't include conditional required dependencies.

Points 1 and 5 are not ideal since the application is used in 2 or 3 files (normal, unit test and speed test).

The point 2 looks fine but there is a possible issue. The config need to be loaded after require.js but moving it after the main.js will possibly make it load after main.js is executed. But I may be wrong here, I need to further investigate this solution.

@millermedeiros
Member

1

<script>
// you can register settings like this before require.js is loaded
var require = {
    baseUrl : 'js'
};
</script>
<script src="js/lib/require.js" data-main="js/main"></script>

2

<script src="js/lib/require.js"></script>
<script src="js/config.js"></script>
<script>
  // same as data-main
  require.config({baseUrl : 'js'});
  require(['js/main']);
</script>

3

This won't affect r.js since it's on the HTML itself not on the JS file, r.js
won't read the HTML file.

<script src="js/lib/require.js"></script>
<script>
  require(['js/config'], function(){
    require(['js/main'])
  });
</script>

There are many options and you could combine things (set a few options on the
markup, others inside a shared script). As I said before best approach will
vary based on your project, sometimes code duplication isn't as bad as it
seems

@idflood
idflood commented Jun 28, 2012

Ohh I see, I didn't try without the "data-main" approach. This gives a lot more flexibility than I first thought, thanks a lot : )

@jrburke jrburke closed this Jun 28, 2012
@jzaefferer

There's still no documentation on this outside of this ticket, is there?

@jrburke
Member
jrburke commented Dec 13, 2012

@jzaefferer not that I know of. If you want to put something together and suggest a location, I am open to it. I suppose this example project demonstrates one the approaches:

https://github.com/requirejs/example-multipage

This is getting into advanced usage, and I'm not sure how best to surface that. Seems like something google search friendly would be good. If that is how you found this, then it may already be set up in the most findable way.

@ghost
ghost commented Mar 28, 2013
<script data-main="js/main" src="js/lib/require.js"></script>


// file: js/main

require(['./global.config'], function(){
    require(['./view/home'], function() {
        // do something
    });
});

This is what I used in my project.

@KBorders01

@07jfxiao Awesome, thanks for the example of #4 above.

@cowwoc
cowwoc commented May 29, 2014

@millermedeiros @jrburke can you please add the examples mentioned at #354 (comment) into http://requirejs.org/docs/api.html#config ?

I think it's common to need to load require.js, followed by config.js followed by main.js but the aforementioned documentation doesn't really explain how to implement it.

I mean, it provides an example but unlike #354 (comment) it doesn't explain that option 2 is equivalent to data-main. When I read option 2 it made sense, but when I read the originally documentation it wasn't obvious this is the case. Please make the documentation more explicit as you did above.

@cowwoc
cowwoc commented May 29, 2014

FYI, here is another trick I just used:

I defined require-config.js as:

// See http://stackoverflow.com/a/22745553/14731
var scripts = document.getElementsByTagName('script');
var thisScriptTag = scripts[ scripts.length - 1 ];
var dataMain = thisScriptTag.getAttribute("data-main");

require.config(
{
  ...
});
if (dataMain)
    require([dataMain]);

Then in the HTML file I have:

        <script type="text/javascript" src="js/require.js"></script>
        <script type="text/javascript" src="js/require-config.js" data-main="js/CompaniesHtml.js"></script>

In other words, you can now specify a data-main on require-config.js instead of on require.js.

@jzaefferer

I suggest reopening this ticket, since there's apparently still a need for public documentation.

To add to the pile of options, here's what I used in a recent project, to be able to reuse the config between app, test and optimizer.

In src/require-config.js:

(function() {
    // :-(
    // make this file work in the mainConfigFile option as well as directly in the browser
    if (!window.require) {
        window.require = {
            config: function(config) {
                window.require = config;
            }
        };
    }

    require.config({
        paths: { ... }
    });
}());

Then in index.html:

<script src="require-config.js"></script>
<script src="../libs/require.js" data-main="app"></script>

Then in a test/layout.html (one specific test page):

<script src="../src/require-config.js"></script>
<script>
require.baseUrl = "../src";
</script>
<script src="../libs/require.js" data-main="../test/layout"></script>

And finally in the grunt-requirejs options we'd reference the same file again, again with an override for baseUrl:

appDir: 'src',
baseUrl: './',
dir: 'dist',
mainConfigFile: 'src/require-config.js'

The need to override baseUrl and the hackity hack around window.require is rather annoying.

@jrburke
Member
jrburke commented Jun 1, 2014

I created this wiki page, feel free to add to the patterns:

https://github.com/jrburke/requirejs/wiki/Patterns-for-separating-config-from-the-main-module

and i linked to it in the into section of the config options.

@czarpino

Area there any plans to standardize/recommend a particular approach? If there is, I would suggest the following:

// homepage.html
<script data-id="requirejs" data-logic="home" data-main="js/config" src="js/lib/require.js"></script>

In, config.js

require.config({
    baseUrl: 'js'
});

// Load app logic as specified in `data-logic` attribute
require([document.querySelector('script[data-id="requirejs"]').getAttribute("data-logic")]);

A short writeup on this is at https://gist.github.com/czarpino/3b06134ac6f43e4b16f5.

@cowwoc
cowwoc commented Jun 12, 2014

@czarpino why not keep data-main for the application code and add data-config for require.config()?

@czarpino

Well, data-main is the entry point for script loading so it makes sense to do configurations there before before any application logic.

@cowwoc
cowwoc commented Jun 13, 2014

@czarpino data-main is already being used for the purpose you want us to use data-logic for. What's the benefit of changing the meaning of this parameter instead of keeping it as-is and adding a new parameter for the configuration?

@MattMcFarland

Why not just create a require config JSON file?? Is that possible? I'm thinking of trying it out.

@czarpino

@cowwoc I don't think there was a change in meaning at all. data-main is for setting configurations and loading the application code while data-logic is for the application code itself.

@cowwoc
cowwoc commented Jun 14, 2014

@czarpino Says who? The documentation doesn't say one way or the other. http://requirejs.org/docs/api.html states that data-main is meant for both the configuration and application entry point. That advice doesn't work for this issue, which is all about decoupling one from the other.

I am in favor of data-main and data-config instead of data-main and data-logic because it isn't immediately obvious that logic refers to the application entry point.

Many programming languages (C/C++/Java) use the terminology main to denote an application's main entry point. I also argue that data-config is very intuitive. It's obvious to anyone reading it that you're going to be pointing to a configuration file.

@DimitarChristoff

+1 @cowwoc data-config - think it was discussed befroe, though and @jrburke said it's not very simple to do.

@millermedeiros
Member

[data-config] is unnecessary, will add complexity and there are many ways to do it. for me the main issue is that config might be cached, you might need to override some settings on different cases (eg. app, test, optimizer) and it will motivate people to do multiple requests in production (in most cases you should really just have a single JS file).

I still believe that the best approach in most cases is to keep the config inside the main.js file and build the app with the mainConfigFile option, so you really just need to duplicate the configs for running the tests, which should not be such a big deal.

@cowwoc
cowwoc commented Jun 16, 2014

@millermedeiros Your solution doesn't solve the requirements of this issue, which is the ability to share the same configuration across multiple scripts. If we only had a single entry point into our application (main.js) then this wouldn't be having this discussion.

@chrisrink

I agree that a data-config attribute would be optimal. This would only be used for development.

For production I would minify my scripts and load them directly.

@tkissing

Kep in mind when trying to share config between dev, build and test, that when requirejs loads, it looks for a variable requirejs and if found uses that as config AND that the rjs optimizer looks for the first call to requirejs() to find the config in a mainConfigFile - no matter where in the file that call is. Combining those two, you can write something like this:

if (typeof requirejs != 'function') {
    requirejs = function(config) { requirejs = config; };
}
requirejs( { paths: { 'underscore': '../lib/lodash/lodash.min' } } );

A config file like this can be included either before or after requirejs and can be used as mainConfigFile for the rjs optimizer.

@mikkotikkanen

+1 to data-config. (and yes, definitely data-main and data-config)

Regardless of the way it's done, there needs to be better way to handle the config as of right now it's creating hard to track issues where, if the config loads fast enough, everything works as they should but on other cases everything breaks. Or just some parts break. This is specially dangerous since these cases usually are client-end devices/connections etc. which there is no direct access to.

Fe. case like this (which is similar to the case which I just spent days debugging 'cos it only manifested on embedded browser loading from remote server):

<script data-main="/js/frontpage.js" src="/vendor/requirejs/require.js"></script>
<script>
    require(['jquery'], function() {
        ....
    });
</script>
<!-- Or even requiring other functionality files here -->

...for any web developer this is the bread and butter - script files block loading so things they provide is available immediately after, as in this case is require as well, so it doesn't even spit out error. However, the config might not be loaded just yet and the jquery gets loaded form the wrong place. If you're running this on local server, the main could even be loaded before anything else so things work as they should. Hell, it might even work correctly on staging server but breaks when pushed to production. Or, breaks when the production is accessed through mobile connections etc.

Whereas if done like so:

<script data-main="/js/frontpage.js" data-config="/js/config.js" src="/vendor/requirejs/require.js"></script>
<script>
    require(['jquery'], function() {
        ....
    });
</script>
<!-- Or even requiring other functionality files here -->

...the data-config could use require to know that there is a config there and it needs to be loaded before anything else is handled. Meanwhile all require requests would go into queue, which is only processed after the config is loaded.

@jac1013
jac1013 commented Aug 11, 2014

data-config and data-main is the way to go for sure IMHO, by the moment I just manage to successfully share a main-config file between many main scripts, using @ghost workaround and deps like this:

<script data-main="js/main.js" src="js/require.js"></script>

main.js has its own require.config() and inside of it we do this:

deps: ['requirejs-main-config']

then in the same main.js file we do as @ghost did:

require(['requirejs-main-config'], function(){
  require(['script1', 'script2'], function(){
    $('#main-component').show(); //just for the sake of the example
  });
});

And this is what we got in our requirejs-main-config file:

require.config({ 
//bunch of configuration here
});
require(['jquery']);

Note that we use $ symbol in main.js even though we didn't require jQuery in the require(['script1', 'script2']) part, jQuery comes from the higher require function, the deps:['requirejs-main-config'] snippet prevent what @mikkotikkanen is explaining above to happen because it stands for These script dependencies should be loaded before loading main.js (quoting the docs) and that's what we are looking for in this case, right?.

EDIT:
Just to clarify the $ symbol working is because I have in my shim configuration of the main.js file that script1 depends on jQuery, if you don't have any definition in your main.js config that any of your scripts need jQuery then it won't be recognized if you don't put it explicitly in your inner require().

Another thing is we actually don't need what @ghost suggested, with deps is more than enough, in case that you aren't using a shim in your main.js configuration you just place jQuery dependency in your main.js deps, here is how it works (refactoring the examples above in case you don't have a shim configuration:

deps: ['requirejs-main-config', 'jQuery']

then in the same main.js file we do:

  require(['script1', 'script2'], function(){
    $('#main-component').show(); //just for the sake of the example
  });

and if you have a shim configuration then it will work like this:

deps: ['requirejs-main-config'],

shim: {
"script1" : {
deps:['jQuery']
}
}

then in the same main.js file we do:

  require(['script1', 'script2'], function(){
    $('#main-component').show(); //just for the sake of the example
  });

This is assuming that requirejs-main-config is requiring not just jQuery but a lot of scripts, otherwise you would require jquery directly in your main.js file.

@DimitarChristoff

In the meanwhile, I now use the following, which allows the config to be consumed before require loads, during (normal nested call) or after, as well as under cjs for build purposes, tests etc:

/*window.require breaks due to context of methods. see http://tobyho.com/2013/03/13/window-prop-vs-global-var/ */
/*jshint -W079*/
var require = (typeof require === 'function' || typeof require === 'object') ? require : {};
(function() {
    'use strict';

    var config = {
        paths: {},
        shim: {},
        exclude: []
    };

    // code coverage should not cover UMD wraps
    /* istanbul ignore next */

    // UMD-like wrap that sets config if possible, exports it or primes it / merges it
    if (typeof define === 'function' && define.amd) {
        // require already loaded, configure above
        require.config(config);
    } else if (typeof module !== 'undefined' && module.exports) {
        // under nodejs, return the object
        module.exports = config;
    } else {
        // if require pre-config exists, this will merge with existing config, overwrites keys
        for (var k in config) {
            // shallow/naive for multiple configs.
            config.hasOwnProperty(k) && (require[k] = config[k]);
        }
    }
}());

so, usage without nested calls is:

<script src="js/require-config.js"></script>
<script src="bower_components/requirejs/require.js" data-main="main"></script>

or

<script src="bower_components/requirejs/require.js"></script>
<script src="js/require-config.js"></script>
<script src="js/main.js"></script>

etc etc.

@melanke melanke referenced this issue in RodneyEbanks/requirejs-plugin-bower May 7, 2015
Open

Questions asked frequently (Qaf)! #1

@RinkAttendant6

None of these configurations on the wiki page work on a site that has a Content Security Policy of script-src lacking 'unsafe-inline'. The closest one is option 4 but that assumes all app entry points are called main.js.

I could make separate config.js files for each page but that defeats the purpose.

Any idea on how to solve this without repeating the config everywhere?

@jrburke
Member
jrburke commented Jul 27, 2015

@RinkAttendant6 I believe https://github.com/requirejs/example-multipage will work in that scenario.

@hagabaka
hagabaka commented Sep 8, 2015

One of the use cases for having a separate require config file is to use it in both the "main" script and a web worker script. In that case, the config file shouldn't reference anything like document, so this rules out a few other setups above that use custom <script data- attributes.

@stavinsky

I have a question about nested require. It's looks like a very good variant, and it works good. But when I'm trying to minify such file using r.js it doesn't find any requirements.
For example this doesn't work with r.js optimization but works in browser:

require(['main'], function (common) {
    require(['router'], function (router) {
        router.initialize();
    });
});

This example works everywhere but here is the problem with async loading. Sometimes main loads after some other requirements and application get fault because it trying to load files from wrong place.

require(['main'], function (common) {
});
require(['router'], function(router){
    router.initialize();
});

UPD:
answer is http://stackoverflow.com/questions/11673320/requirejs-optimizer-does-not-include-nested-require-calls

@tkissing

While that might work, your "Sometimes main loads after some other requirements and application get fault" sounds like you have unexpressed dependencies in one or more modules. In other word, some of your code expects main to have run, but does not actively require it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment