Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load multiple named AMD modules defined in a single file?

My understanding is that it shouldn't happen, but it does. What is the most appropriate workaround for these libraries?

Notes:

  • I figured running these scripts after require.js manually (using a script tag) should work, and indeed it seems like it does. However, the RequireJS documentation explicitly warns that the data-main script is run asynchronously. While require.js should have properly defined the define function needed by the scripts defining multiple named modules, I also assume that without the proper configuration loaded from the data-main script, bad things may happen in a non-deterministic way. Is this correct?
  • I also fail to see how any combination of the shim, map, bundles and paths configuration properties can help in this case, although I hope I'm missing it.

Clarification on the first note: (My bad, it's really not clear)

What I describe here is simply to manually execute (using an HTML script tag) the script that defines multiple modules after RequireJS and the data-main script. With the knowledge that the latter is run async, my worries should become more obvious (but feel free to ask me to detail some more). The bulk of it is that although it seems like I can require each named module successfully, I'm not sure that the behavior is deterministic (also, it's not pretty, I'd much rather avoid additional script tags and properly load everything asynchronously).

<script src="scripts/require.js" data-main="app/main.js"></script>
<script src="scripts/datajs-1.1.2.js"></script>

Here, datajs-1.1.2.js defines two modules, as described in the link above and copied below:

// AMD support
if (typeof define === 'function' && define.amd) {
    define('datajs', datajs);
    define('OData', odata);
} ...
like image 320
tne Avatar asked Feb 03 '14 15:02

tne


People also ask

How does RequireJS load modules?

RequireJS uses Asynchronous Module Loading (AMD) for loading files. Each dependent module will start loading through asynchronous requests in the given order. Even though the file order is considered, we cannot guarantee that the first file is loaded before the second file due to the asynchronous nature.

What is RequireJS used for?

RequireJS is a JavaScript library and file loader which manages the dependencies between JavaScript files and in modular programming. It also helps to improve the speed and quality of the code.

Is RequireJS synchronous?

So, RequireJS doesn't support it. From your use case it seems that you don't need synchronous RequireJS, you need to return result asynchronously. AMD pattern allows to define dependencies and load them asynchronously, but module's factory function must return result synchronously.

What is define () in JS?

The define() function can be used to load the modules (module can be an object, function, class or a code which is executed after loading a module). You can load different versions of the same module in the same page.


1 Answers

What will and will not work depends on the specifics of how the file which defines multiple named modules will be used in an application.

In general, if the order in which modules defined (using named defines) in a single file cannot be determined, then setting paths to map the module names to the file that defines them should prevent problems:

paths: {
    'foo': 'path/to/foobar',
    'bar': 'path/to/foobar'
}

If foo or bar are required, RequireJS will load the file that defines both (path/to/foobar.js), which is not a problem.

With the details that you've added to the question, I can say this. First, this code:

<script src="scripts/require.js" data-main="app/main.js"></script>
<script src="scripts/datajs-1.1.2.js"></script>

is incorrect. Loading a module that calls define through a <script> tag is generally wrong. (I would say it is always wrong, but there may be some really strange cases where to get incompatible assets to work together you have to do something that would normally be wrong. But this is unusual, and has to be justified.) As you suggested by doing this, you open yourself to timing issues. Sometimes it may work, sometimes it may not.

However, this should prevent any timing issues:

<script>
require = {
    paths: {
        datajs: 'scripts/datajs-1.1.2',
        OData: 'scripts/datajs-1.1.2'
    }
};
</script>
<script src="scripts/require.js" data-main="app/main.js"></script>

Whenever anything needs either of the two modules in datajs-1.1.2.js, either because it called require or because it called define with the appropriate module names, the file that defines both modules is going to be loaded.

(Warning: the configuration I show in the example above is an educated guess, which contains enough details to illustrate. It may not work once combined with a configuration already present in app/main.js, and I'm not suggesting that it is the best way to configure RequireJS for your specific application.)

For RequireJS version 2.1.10 and higher, there's also the bundles option, which is nicer to use:

<script>
require = {
    bundles: {
      "js/datajs-1.1.2": ["datajs", "OData"]
    }
};
</script>
<script src="scripts/require.js" data-main="app/main.js"></script>

I suggest reading the documentation on this option to avoid possible misunderstandings as to how it works.

like image 137
Louis Avatar answered Sep 19 '22 18:09

Louis