Stage 3 Draft / December 12, 2023

JSON modules

See the explainer for information.

Editor's Note

This proposal is buit on top of the Import Attributes proposal.

1 Semantics

1.1 HostLoadImportedModule ( referrer, moduleRequest, hostDefined, payload )

The host-defined abstract operation HostLoadImportedModule takes arguments referrer (a Script Record, a Cyclic Module Record, or a Realm Record), moduleRequest (a ModuleRequest Record), hostDefined (anything), and payload (a GraphLoadingState Record or a PromiseCapability Record) and returns unused.

Note 1

An example of when referrer can be a Realm Record 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.

An implementation of HostLoadImportedModule must conform to the following requirements:

The actual process performed is host-defined, but typically consists of performing whatever I/O operations are necessary to load the appropriate Module Record. Multiple different (referrer, moduleRequest.[[Specifer]], moduleRequest.[[Attributes]]) triples may map to the same Module Record instance. The actual mapping semantics is host-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 expansion of relative and abbreviated path specifiers.

Note 2

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) plan to require with { type: "json" }, and environments which want to restrict themselves to a compatible subset would do so as well.

Note 3

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

1.2 Synthetic Module Records

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 )

The abstract operation CreateSyntheticModule takes arguments exportNames (a List of Strings without duplicates), evaluationSteps (an Abstract Closure), and realm (a Realm Record) and returns a Synthetic Module Record. It creates a Synthetic Module Record based upon the given exported names and evaluation steps. It performs the following steps when called:

  1. Return Synthetic Module Record { [[Realm]]: realm, [[Environment]]: empty, [[Namespace]]: empty, [[HostDefined]]: empty, [[ExportNames]]: exportNames, [[EvaluationSteps]]: evaluationSteps }.
Editor's Note
It seems we could set up the environment either here or in Link(). I've chosen to do so in Link() for symmetry with Cyclic Module Records (and thus 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 takes arguments module (a Synthetic Module Record), exportName (a String), and exportValue (an ECMAScript Language value) and returns unused. It 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 when called:

  1. Let envRec be module.[[Environment]].
  2. Assert: envRec is not empty.
  3. Perform envRec.SetMutableBinding(exportName, exportValue, true).
  4. Return unused.

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 LoadRequestedModules ( )

The LoadRequestedModules concrete method of a Synthetic Module Record module takes no arguments and returns a Promise. It performs the following steps when called:

  1. Return ! PromiseResolve(%Promise%, undefined).
Note
Synthetic Module Records have no dependencies.

1.2.3.2 GetExportedNames ( )

The GetExportedNames concrete method of a Synthetic Module Record module takes no arguments and returns a List of Strings. It performs the following steps when called:

  1. Return module.[[ExportNames]].

1.2.3.3 ResolveExport ( exportName )

The ResolveExport concrete method of a Synthetic Module Record module takes argument exportName (a String) and returns a ResolvedBinding Record or null. It performs the following steps when called:

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

1.2.3.4 Link ( )

The Link concrete method of a Synthetic Module Record module takes no arguments and returns unused. It performs the following steps when called:

  1. Let realm be module.[[Realm]].
  2. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
  3. Set module.[[Environment]] to env.
  4. For each String exportName in module.[[ExportNames]], do
    1. Perform ! env.CreateMutableBinding(exportName, false).
    2. Perform ! env.InitializeBinding(exportName, undefined).
  5. Return unused.

1.2.3.5 Evaluate ( )

The Evaluate concrete method of a Synthetic Module Record module takes no arguments and returns a Promise. It performs the following steps when called:

  1. Let moduleContext be a new ECMAScript code execution context.
  2. Set the Function of moduleContext to null.
  3. Set the Realm of moduleContext to module.[[Realm]].
  4. Set the ScriptOrModule of moduleContext to module.
  5. Set the VariableEnvironment of moduleContext to module.[[Environment]].
  6. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
  7. Suspend the currently running execution context.
  8. Push moduleContext on to the execution context stack; moduleContext is now the running execution context.
  9. Let steps be module.[[EvaluationSteps]].
  10. Let result be Completion(steps(module)).
  11. Suspend moduleContext and remove it from the execution context stack.
  12. Resume the context that is now on the top of the execution context stack as the running execution context.
  13. Let pc be ! NewPromiseCapability(%Promise%).
  14. IfAbruptRejectPromise(result, pc).
  15. Perform ! pc.[[Resolve]](result).
  16. Return pc.[[Promise]].

1.2.4 CreateDefaultExportSyntheticModule ( defaultExport )

The abstract operation CreateDefaultExportSyntheticModule takes argument defaultExport (an ECMAScript Language value) and returns a Synthetic Module Record. It creates a Synthetic Module Record whose default export is defaultExport. It performs the following steps when called:

  1. Let realm be the current Realm Record.
  2. Let setDefaultExport be a new Abstract Closure with parameters (module) that captures defaultExport and performs the following steps when called:
    1. Perform SetSyntheticModuleExport(module, "default", defaultExport).
  3. Return CreateSyntheticModule"default" », setDefaultExport, realm).

1.2.5 ParseJSONModule ( source )

The abstract operation ParseJSONModule takes argument source (a String) and returns either a normal completion containing a Synthetic Module Record, or a throw completion. It performs the following steps when called:

  1. Let json be ? Call(%JSON.parse%, undefined, « source »).
  2. Return CreateDefaultExportSyntheticModule(json).
Editor's Note
TODO: Refactor JSONParse into an abstract algorithm.