Stage 3 Draft / February 6, 2021

JSON modules

See the explainer for information.

1 Semantics

1.1 Runtime Semantics: HostResolveImportedModule ( referencingScriptOrModule, moduleRequest )

Note 1

The changes made to this abstract operation in this proposal build on the changes in the import assertions proposal. In order to distingush them, the changes unique to this proposal are labeled as insertions while the changes from import assertions are not labeled.

HostResolveImportedModule is an implementation-defined abstract operation that provides the concrete Module Record subclass instance that corresponds to the ModuleRequest Record moduleRequest occurring within the context of the script or module represented by the Script Record or Module Record referencingScriptOrModule. referencingScriptOrModule may also be null, if the resolution is being performed in the context of an import() expression, and there is no active script or module at that time.

Note 2

An example of when referencingScriptOrModule can be null is in a web browser host. There, if a user clicks on a control given by

<button type="button" onclick="import('./foo.mjs')">Click me</button>

there will be no active script or module at the time the import() expression runs. More generally, this can happen in any situation where the host pushes execution contexts with null ScriptOrModule components onto the execution context stack.

The implementation of HostResolveImportedModule must conform to the following requirements:

  • The normal return value must be an instance of a concrete subclass of Module Record.
  • If a Module Record corresponding to the pair referencingScriptOrModule, moduleRequestdoes not exist or cannot be created, an exception must be thrown.
  • Each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]], moduleRequest.[[Assertions]] triple as arguments it must return the same Module Record instance if it completes normally.
    • It is recommended but not required that when possible, implementations additionally conform to the following stronger constraint: each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]] pair as arguments it must return the same Module Record instance if it completes normally.
  • moduleRequest.[[Assertions]] must not influence the interpretation of the module or the module specifier; instead, it may be used to determine whether the algorithm completes normally or with an abrupt completion.
  • If assertions has an entry entry such that entry.[[Key]] is "type", let type be entry.[[Value]]. The following requirements apply:

Multiple different referencingScriptOrModule, moduleRequest.[[Specifier]] pairs may map to the same Module Record instance. The actual mapping semantic is implementation-defined but typically a normalization process is applied to specifier as part of the mapping process. A typical normalization process would include actions such as alphabetic case folding and expansion of relative and abbreviated path specifiers.

Editor's Note

The above text implies that is recommended but not required that hosts do not use moduleRequest.[[Assertions]] as part of the module cache key. In either case, an exception thrown from an import with a given assertion list does not rule out success of another import with the same specifier but a different assertion list.

Assertions do not affect the contents of the module. Future follow-up proposals may relax this restriction with "evaluator attributes" that would change the contents of the module. There are three possible ways to handle multiple imports of the same module with "evaluator attributes":

  • Race and use the attribute that was requested by the first import. This seems broken--the second usage is ignored.
  • Reject the module graph and don't load if attributes differ. This seems bad for composition--using two unrelated packages together could break, if they load the same module with disagreeing attributes.
  • Clone and make two copies of the module, for the different ways it's transformed. In this case, the attributes would have to be part of the cache key. These semantics would run counter to the intuition that there is just one copy of a module.

It's possible that one of these three options may make sense for a module load, on a case-by-case basis by attribute, but it's worth careful thought before making this choice.

Editor's Note

The above text implies that hosts must support JSON modules imported with type: "json" (if it completes normally), but it doesn't prohibit hosts from supporting JSON modules imported with no type specified. Some environments (for example, web browsers) are expected to require assert { type: "json" }, and environments which want to restrict themselves to a compatible subset would do so as well.

Editor's Note

All of the import statements in the module graph that address the same JSON module will evaluate to the same mutable object.

1.2 Synthetic Module Records

Editor's Note
This Synthetic Module Records specification text comes from the JavaScript Standard Library proposal and was written by Domenic Denicola. A version of this text exists in WebIDL. A version of the text is copied here to clarify that the concept of Synthetic Module Records can proceed in standardization independently of the built-in modules effort.

A Synthetic Module Record is used to represent information about a module that is defined by specifications. Its exports are derived from a pair of lists, of string keys and of ECMAScript values. The set of exported names is static, and determined at creation time (as an argument to CreateSyntheticModule), while the set of exported values can be changed over time using SetSyntheticModuleExport. It has no imports or dependencies.

Note
A Synthetic Module Record could be used for defining a variety of module types: for example, built-in modules, or JSON modules, or CSS modules.

In addition to the fields defined in Table 40, Synthetic Module Records have the additional fields listed in Table 1. Each of these fields is initially set in CreateSyntheticModule.

Table 1: Additional Fields of Synthetic Module Records
Field Name Value Type Meaning
[[ExportNames]] List of String A List of all names that are exported.
[[EvaluationSteps]] An Abstract Closure An Abstract Closure that will be performed upon evaluation of the module, taking the Synthetic Module Record as its sole argument. These will usually set up the exported values, by using SetSyntheticModuleExport. They must not modify [[ExportNames]]. They may return an abrupt completion.

1.2.1 CreateSyntheticModule ( exportNames, evaluationSteps, realm, hostDefined )

The abstract operation CreateSyntheticModule creates a Synthetic Module Record based upon the given exported names and evaluation steps. It performs the following steps:

  1. Return Synthetic Module Record { [[Realm]]: realm, [[Environment]]: undefined, [[Namespace]]: undefined, [[HostDefined]]: hostDefined, [[ExportNames]]: exportNames, [[EvaluationSteps]]: evaluationSteps }.
Editor's Note
It seems we could set up the environment either here or in Instantiate(). I've chosen to do so in Instantiate() for symmetry with Source Text Module Records, but I don't think there's any actual requirement in that regard.

1.2.2 SetSyntheticModuleExport ( module, exportName, exportValue )

The abstract operation SetSyntheticModuleExport can be used to set or change the exported value for a pre-established export of a Synthetic Module Record. It performs the following steps:

  1. Let envRec be module.[[Environment]]'s EnvironmentRecord.
  2. Perform envRec.SetMutableBinding(exportName, exportValue, true).

1.2.3 Concrete Methods

The following are the concrete methods for Synthetic Module Record that implement the corresponding Module Record abstract methods.

Editor's Note
I find having this wrapping sub-clause cleaner and suggest we do the same for Source Text Module Records in the main spec.

1.2.3.1 GetExportedNames( exportStarSet )

The GetExportedNames concrete method of a Synthetic Module Record implements the corresponding Module Record abstract method.

It performs the following steps:

  1. Let module be this Synthetic Module Record.
  2. Return module.[[ExportNames]].

1.2.3.2 ResolveExport( exportName, resolveSet )

The ResolveExport concrete method of a Synthetic Module Record implements the corresponding Module Record abstract method.

It performs the following steps:

  1. Let module be this Synthetic Module Record.
  2. If module.[[ExportNames]] does not contain exportName, return null.
  3. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: exportName }.

1.2.3.3 Instantiate ( )

The Instantiate concrete method of a Synthetic Module Record implements the corresponding Module Record abstract method.

It performs the following steps:

  1. Let module be this Synthetic Module Record.
  2. Let realm be module.[[Realm]].
  3. Assert: realm is not undefined.
  4. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
  5. Set module.[[Environment]] to env.
  6. Let envRec be env's EnvironmentRecord.
  7. For each exportName in module.[[ExportNames]],
  8. Perform ! envRec.CreateMutableBinding(exportName, false).
  9. Perform ! envRec.InitializeBinding(exportName, undefined).
  10. Return undefined.

1.2.3.4 Evaluate ( )

The Evaluate concrete method of a Synthetic Module Record implements the corresponding Module Record abstract method.

It performs the following steps:

  1. Let module be this Synthetic Module Record.
  2. Let moduleCxt be a new ECMAScript code execution context.
  3. Set the Function of moduleCxt to null.
  4. Assert: module.[[Realm]] is not undefined.
  5. Set the Realm of moduleCxt to module.[[Realm]].
  6. Set the ScriptOrModule of moduleCxt to module.
  7. Set the VariableEnvironment of moduleCxt to module.[[Environment]].
  8. Set the LexicalEnvironment of moduleCxt to module.[[Environment]].
  9. Suspend the currently running execution context.
  10. Push moduleCxt on to the execution context stack; moduleCxt is now the running execution context.
  11. Let result be the result of performing ? module.[[EvaluationSteps]](module).
  12. Suspend moduleCxt and remove it from the execution context stack.
  13. Resume the context that is now on the top of the execution context stack as the running execution context.
  14. Return Completion(result).

1.3 CreateDefaultExportSyntheticModule ( defaultExport )

The CreateDefaultExportSyntheticModule abstract operation creates a Synthetic Module Record whose default export is defaultExport. It performs the following steps:

  1. Return CreateSyntheticModule"default" », the following steps, the current Realm, defaultExport) with the following steps given module as an argument:

    1. SetSyntheticModuleExport(module, "default", module.[[HostDefined]]).

1.4 ParseJSONModule ( source )

The abstract operation ParseJSONModule takes a single argument source which is a String representing the contents of a module.

  1. Let json be ? Call(%JSON.parse%, undefined, « source »).
  2. Return CreateDefaultExportSyntheticModule(json).