multiple modules with the same name

# Marius Gundersen (11 years ago)

I didn't find anything in the spec on handling multiple modules with the same name, or how to handle the redefinition of an existing module. At any time a <script type="module" name="existing-name">export default "I just replaced an exsting module";</script> element could be added to the dom, which would replace an existing module. This could also happen with pure JavaScript, by calling any of the Loader.prototype.define, Loader.prototype.import, Loader.prototype.load or Loader.prototype.set methods. What should happen in such a scenario? Should existing modules be replaced? Should an error be thrown? How would that work with the DOM? Should it be a no-op, with no feedback to the user?

# Jason Orendorff (11 years ago)

On Mon, Jan 27, 2014 at 1:54 PM, Marius Gundersen <gundersen at gmail.com> wrote:

I didn't find anything in the spec on handling multiple modules with the same name [...] What should happen in such a scenario? Should existing modules be replaced? Should an error be thrown? How would that work with the DOM? Should it be a no-op, with no feedback to the user?

Before a module is ever exposed to scripts, before its module body runs, it is first linked. This binds it permanently with the other modules it imports.

After that, if you call loader.set(name, otherModule), that only changes the loader's module registry. It affects future module loads. It does not affect any modules already linked to the original module that you replaced. It does not affect any functions from that original module that are already on the stack.

# Marius Gundersen (11 years ago)

So then there would be two versions of the same module, and a module could get access to both these modules at the same time. For example, if ModuleB, which depends on ModuleA is loaded and linked, and later ModuleA is redefined, then ModuleC could depend on both ModuleB and ModueA, and would get (indirect) access to two versions of ModuleA. Is this problem preventable?

# David Herman (11 years ago)

It's important to be able to modify module registration for things like polyfilling. But that doesn't mean it's something people should do in general. Note that Jason only gave you an answer in the context of the basic module loader semantics; he didn't say what will happen in the HTML semantics. In particular, we can make it an error for there to be two definitions of the same module name in the same HTML, a la:

<script type="module" name="jquery" src="jquery1.js"></script>
<script type="module" name="jquery" src="jquery2.js"></script>

I'm inclined to call that an error, and require imperative modifications of existing module registrations to use imperative code.

# John Barton (11 years ago)

What is the use case for allowing registration different modules under the same name? IMO should be an error.

# Sam Tobin-Hochstadt (11 years ago)

This is absolutely necessary for polyfilling.

Imagine that some browser has an ok-but-not-complete implementation of the X library, but you want to use jQuery 17, which requires a better version. You need to be able to replace X with a polyfilled update to X, and then load jQuery on top of that.

Note that this involves indirect access in the same library (jQuery) to two versions of X (the polyfill and the browser version), which is why I don't think Marius's worry is fixable without throwing out the baby with the bathwater.

# John Barton (11 years ago)

Guy Bedford, based on experiences within the requirejs and commonjs worlds, has a much better solution for these scenarios. (It's also similar to how npm works).

Your jQuery should depend upon the name X, but you Loader should map the name X when loaded by jQuery to the new version in Loader.normalize(). The table of name mappings can be configured at run time.

For example, if some other code depends on X at 1.6 and jQuery needs X at 1.7, they each load exactly the version they need because the normalized module names embed the version number.

This is the proper solution, not one based on overwriting the module registry. To be sure, because of the overhead loading code on slow networks these kinds of multi-version scenarios are less attractive in the browser, but the fix is the adapt the code.

Guy's project has a bit more: guybedford/systemjs

# Marius Gundersen (11 years ago)

Guy Bedford, based on experiences within the requirejs and commonjs worlds, has a much better solution for these scenarios. (It's also similar to how npm works).

Your jQuery should depend upon the name X, but you Loader should map the name X when loaded by jQuery to the new version in Loader.normalize(). The table of name mappings can be configured at run time.

For example, if some other code depends on X at 1.6 and jQuery needs X at 1.7, they each load exactly the version they need because the normalized module names embed the version number.

This seems to handle the scenario where two versions is wanted. This would probably also work in the scenario where a unique instance of the same version should be given to every module depending on it. This would be useful for unit tests, where a clean world is required for each tests. Or for unit test maybe a new realm should be used for each test.

If a module is redefined with imperative code then an error should probably be thrown, or the promise could be rejected. But I don't think that would be good for DOM manipulation. If innerHTML is used to add a lot of markup and an existing module to the DOM, should an error be thrown? Should none of the other markup be added to the DOM? What would the error message look like to adequately indicate the source of the error?

# John Barton (11 years ago)

On Mon, Jan 27, 2014 at 3:24 PM, Marius Gundersen <gundersen at gmail.com>wrote:

But I don't think that would be good for DOM manipulation. If innerHTML is used to add a lot of markup and an existing module to the DOM, should an error be thrown?

In my opinion, attempting to overwrite a Loader's module entry would cause an error yes.

Should none of the other markup be added to the DOM?

Whatever DOM does is DOM's business.

What would the error message look like to adequately indicate the source of the error?

The module name ${moduleName} has already been defined.

I don't suggest we try to issue an error message special to this circumstance, such as "Adding a module via innerHTML is craziness". We'll just let folks work that part out themselves.

# James Burke (11 years ago)

On Mon, Jan 27, 2014 at 3:14 PM, John Barton <johnjbarton at google.com> wrote:

Guy Bedford, based on experiences within the requirejs and commonjs worlds, has a much better solution for these scenarios. (It's also similar to how npm works).

Your jQuery should depend upon the name X, but you Loader should map the name X when loaded by jQuery to the new version in Loader.normalize(). The table of name mappings can be configured at run time.

For example, if some other code depends on X at 1.6 and jQuery needs X at 1.7, they each load exactly the version they need because the normalized module names embed the version number.

In the AMD world, map config has been sufficient for these needs.

As a point of reference, requirejs only lets the first module registration win, any subsequent registrations for the same module ID are ignored. “ignore" was chosen over “error" because with multiple module bundles, the same module ID/definition could show up in two different bundles (think multi-page apps that have a “common” and page-specific bundles).

I do not believe that case should trigger an error. It is just inefficient, and tooling for bundles could offer to enforce finding these inefficiencies vs the browser stopping the app from working by throwing an error.

It is true that the errors introduced by “ignore” could be harder to detect given that things may mostly work. The general guide in this case for requirejs was to be flexible in the same spirit of HTML parsing.

Redefinition seems to allow breaking the expectation that the module value for a given normalized ID is always the same.

When the developer wants to explicitly orchestrate different module values for specific module ID sets, something like AMD’s map config is a good solution as it is a more declarative statement of intent vs code in a module body deciding to redefine.

Also, code in module bodies do not have enough information to properly redefine for a set of module IDs like map config can. Map config has been really useful for supporting two different versions of a module in an app, and for providing mocks to certain modules for unit testing.

Given what has been said so far for use cases, I would prefer either “ignore” or “error” over redefinition, with a preference of “ignore” over “error" based on the above.

# Maciej Jaros (11 years ago)

Sam Tobin-Hochstadt (2014-01-27 23:50):

This is absolutely necessary for polyfilling.

Imagine that some browser has an ok-but-not-complete implementation of the X library, but you want to use jQuery 17, which requires a better version. You need to be able to replace X with a polyfilled update to X, and then load jQuery on top of that.

Note that this involves indirect access in the same library (jQuery) to two versions of X (the polyfill and the browser version), which is why I don't think Marius's worry is fixable without throwing out the baby with the bathwater.

Keeping your syntax you could declare:

<script type="module" name="jquery_1_17" src="jquery1.js"></script>
<script type="module" name="jquery_2_1" src="jquery2.js"></script>

Then the script that need 1.17 uses import:

import $ from "jquery_1_17";

And script that need 2.1:

import $ from "jquery_2_1";

But to my understanding a loader could be created that could automatically resolve something like (i.e. per some convention it would now where to find best CDN for each jQuery version):

import $ from "cdn.jquery/2.1";

Or just a full URL:

import $ from "http://code.jquery.com/jquery-2.1.0.min.js"

I actually like that syntax much more then declaring stuff in HTML. Not sure if that will be possible though.

# Marius Gundersen (11 years ago)

On Tue, Jan 28, 2014 at 2:13 AM, James Burke <jrburke at gmail.com> wrote:

In the AMD world, map config has been sufficient for these needs[1].

As a point of reference, requirejs only lets the first module registration win, any subsequent registrations for the same module ID are ignored. "ignore" was chosen over "error" because with multiple module bundles, the same module ID/definition could show up in two different bundles (think multi-page apps that have a "common" and page-specific bundles).

AFAIK ES-6 modules cannot be bundled (yet). But if/when they can be bundled this is an argument for silently ignoring duplicates

I do not believe that case should trigger an error. It is just inefficient, and tooling for bundles could offer to enforce finding these inefficiencies vs the browser stopping the app from working by throwing an error.

It is true that the errors introduced by "ignore" could be harder to detect given that things may mostly work. The general guide in this case for requirejs was to be flexible in the same spirit of HTML parsing.

Since the imperative API through the Loader object uses promises, there is the option to reject the promise rather than throwing an error. This would let the developer handle the duplicate if they want to, but wouldn't require wrapping the API calls in try{}catch.

# James Burke (11 years ago)

On Mon, Jan 27, 2014 at 11:53 PM, Marius Gundersen <gundersen at gmail.com> wrote:

AFAIK ES-6 modules cannot be bundled (yet). But if/when they can be bundled this is an argument for silently ignoring duplicates

Loader.prototype.define seems to allow bundling, by passing strings for the module bodies:

people.mozilla.org/~jorendorff/js-loaders/Loader.html