Module singletons

# Raul-Sebastian Mihăilă (7 years ago)
  1. My understanding is that a module is a singleton. But how does the spec enforce that? Section 15.2.1.17 HostResolveImportedModule ( tc39.github.io/ecma262/#sec-hostresolveimportedmodule) says that the resolving process of a module must be indempotent. 'Each time it is called with a specific referencingModule, specifier pair as arguments it must return the same Module Record instance.'

But in the following scenario:

main.js

import a from 'a.js';
import b from 'b.js';

a.js

impot c from 'c.js';

console.log(c);

b.js

import c from 'c.js';

console.log(c);

c.js

export default 3;

can we be certain that c.js resolves to the same module record and that both a.js and b.js print 3? There are two different referencingModule, specifier pairs even though the specifier is the same.

  1. Is it correct that it's mandatory for an implementation to produce different module instances in every TopLevelModuleEvaluationJobs? So if we have two script tags with type module and with the same script content will they always produce different module instances or is it possible for an implementation to produce modules without executing a TopLevelModuleEvaluationJob and reuse module instances?
# Isiah Meadows (7 years ago)

I'm going purely based on memory, but if I recall correctly, the module name resolution is left entirely to the implementation. That decides whether two specifiers from two different modules point to the same module.

(Technically, two modules could even share a file path without being in violation of the spec. Specifiers are unique, but the spec makes no normative mention of file paths.)

# Romuald Quantin (7 years ago)

Usually solved this way:

  • export something to instantiate (a function or a class)
module.exports = MyClass;
  • export an object (will be shared, only one instance)
module.exports = {};
  • export an object to facilitate the creation of a new instance every time it is called
module.exports = {
    create: () => {
        return new MyClass();
    }
};
# Raul-Sebastian Mihăilă (7 years ago)

On Thu, Feb 9, 2017 at 4:41 PM, Romuald Quantin <romu at soundstep.com> wrote:

Usually solved this way:

  • export something to instantiate (a function or a class)
module.exports = MyClass;
  • export an object (will be shared, only one instance)
module.exports = {};
  • export a new instance every time
module.exports = new MyClass();

My question was about some spec details wrt standard ES modules, not about nodejs.

# Allen Wirfs-Brock (7 years ago)

On Feb 9, 2017, at 1:37 AM, Raul-Sebastian Mihăilă <raul.mihaila at gmail.com> wrote:

  1. My understanding is that a module is a singleton. But how does the spec enforce that? Section 15.2.1.17 HostResolveImportedModule (tc39.github.io/ecma262/#sec-hostresolveimportedmodule, tc39.github.io/ecma262/#sec-hostresolveimportedmodule) says that the resolving process of a module must be indempotent. 'Each time it is called with a specific referencingModule, specifier pair as arguments it must return the same Module Record instance.’

It is up to the host environment to define what constitutes distinct external module resources, how module specifiers are interpreted, and how referencingModule/specifier pairs map to resources. However, underlying intent is that each distinct module resource is loaded and initialized only once.

But in the following scenario:

main.js

import a from 'a.js';
import b from 'b.js';

a.js

impot c from 'c.js';

console.log(c);

b.js

import c from 'c.js';

console.log(c);

c.js

export default 3;

can we be certain that c.js resolves to the same module record and that both a.js and b.js print 3? There are two different referencingModule, specifier pairs even though the specifier is the same.

The answer to this must be provided by the host when it publishes its rules for interpreting module specifiers. If the host environment only provided a flat namespace of module resources (or if specifiers of the form “foo.js” all map to a common set of module resources) then the two imports of “c.js” in the example should resolve to the same module record. This is what I would expect from this example, assuming that the host interprets module specifiers in a manner similar to file paths

If the initial imports were

main.js

import a from ‘A/a.js';
import b from ‘B/b.js';

and the imports of c.js were:

import c from ‘./c.js’;

I would expect that most hosts would interpret the specifiers such that two different “c” modules would be loaded (or an error would occurs if two distinct module resources for “c.js” didn’t exist).

  1. Is it correct that it's mandatory for an implementation to produce different module instances in every TopLevelModuleEvaluationJobs? So if we have two script tags with type module and with the same script content will they always produce different module instances or is it possible for an implementation to produce modules without executing a TopLevelModuleEvaluationJob and reuse module instances?

The expectation is that a host will only enqueue one TopLevelEvaluationJob for each distinct top level module resource. Essentially the host is expected to do the same sort of canonicalization of module specifiers (relative to a ”null” referencing module) that is done by HostResolveImportedModule. Redundant top level references of the same module resource should be ignored.

I’m not sure what the proposed HTML spec. says, but I would expect that two module script tags that have a src attribute that resolves to the same resource only do a single load of the module. I would expect each module script tag that lacks a src attribute to be treated as a distinct module resource, even if two such tags contain identical source code