Stage 2 Draft / February 26, 2025

Deferred Imports Evaluation

The diff markers in this proposal are on top of tc39/ecma262#3057 (Import Attributes).

10 Ordinary and Exotic Objects Behaviours

10.4 Built-in Exotic Object Internal Methods and Slots

This specification defines several kinds of built-in exotic objects. These objects generally behave similar to ordinary objects except for a few specific situations. The following exotic objects use the ordinary object internal methods except where it is explicitly specified otherwise below:

10.4.6 Module Namespace Exotic Objects

A module namespace exotic object is an exotic object that exposes the bindings exported from an ECMAScript Module (See 16.2.3). There is a one-to-one correspondence between the String-keyed own properties of a module namespace exotic object and the binding names exported by the Module. The exported bindings include any bindings that are indirectly exported using export * export items. Each String-valued own property key is the StringValue of the corresponding exported binding name. These are the only String-keyed properties of a module namespace exotic object. Each such property has the attributes { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }. Module namespace exotic objects are not extensible.

An object is a module namespace exotic object if its [[GetPrototypeOf]], [[SetPrototypeOf]], [[IsExtensible]], [[PreventExtensions]], [[GetOwnProperty]], [[DefineOwnProperty]], [[HasProperty]], [[Get]], [[Set]], [[Delete]], and [[OwnPropertyKeys]] internal methods use the definitions in this section, and its other essential internal methods use the definitions found in 10.1. These methods are installed by ModuleNamespaceCreate.

Module namespace exotic objects have the internal slots defined in Table 1.

Table 1: Internal Slots of Module Namespace Exotic Objects
Internal Slot Type Description
[[Module]] a Module Record The Module Record whose exports this namespace exposes.
[[Exports]] a List of Strings A List whose elements are the String values of the exported names exposed as own properties of this object. The list is ordered as if an Array of those String values had been sorted using %Array.prototype.sort% using undefined as comparefn.
[[Deferred]] a Boolean Whether this module namespace was obtained through import defer/import.defer(). Deferred namespaces can have side effects when accessing properties on them.

10.4.6.5 [[GetOwnProperty]] ( P )

The [[GetOwnProperty]] internal method of a module namespace exotic object O takes argument P (a property key) and returns either a normal completion containing either a Property Descriptor or undefined, or a throw completion. It performs the following steps when called:

  1. If P is a SymbolIsSymbolLikeNamespaceKey(P, O) is true, return OrdinaryGetOwnProperty(O, P).
  2. Let exports be O.[[Exports]]GetModuleExportsList(O).
  3. If exports does not contain P, return undefined.
  4. Let value be ? O.[[Get]](P, O).
  5. Return PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }.

10.4.6.6 [[DefineOwnProperty]] ( P, Desc )

The [[DefineOwnProperty]] internal method of a module namespace exotic object O takes arguments P (a property key) and Desc (a Property Descriptor) and returns either a normal completion containing a Boolean or a throw completion. It performs the following steps when called:

  1. If P is a SymbolIsSymbolLikeNamespaceKey(P, O), return ! OrdinaryDefineOwnProperty(O, P, Desc).
  2. Let current be ? O.[[GetOwnProperty]](P).
  3. NOTE: If O.[[Deferred]] is true, the step above will ensure that the module is evaluated.
  4. If current is undefined, return false.
  5. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is true, return false.
  6. If Desc has an [[Enumerable]] field and Desc.[[Enumerable]] is false, return false.
  7. If IsAccessorDescriptor(Desc) is true, return false.
  8. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, return false.
  9. If Desc has a [[Value]] field, return SameValue(Desc.[[Value]], current.[[Value]]).
  10. Return true.

10.4.6.7 [[HasProperty]] ( P )

The [[HasProperty]] internal method of a module namespace exotic object O takes argument P (a property key) and returns a normal completion containing a Boolean. It performs the following steps when called:

  1. If P is a SymbolIsSymbolLikeNamespaceKey(P, O), return ! OrdinaryHasProperty(O, P).
  2. Let exports be O.[[Exports]]GetModuleExportsList(O).
  3. If exports contains P, return true.
  4. Return false.

10.4.6.8 [[Get]] ( P, Receiver )

The [[Get]] internal method of a module namespace exotic object O takes arguments P (a property key) and Receiver (an ECMAScript language value) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:

  1. If P is a SymbolIsSymbolLikeNamespaceKey(P, O), return ! OrdinaryGet(O, P, Receiver).
  2. Let exports be O.[[Exports]]GetModuleExportsList(O).
  3. If exports does not contain P, return undefined.
  4. Let m be O.[[Module]].
  5. Let binding be m.ResolveExport(P).
  6. Assert: binding is a ResolvedBinding Record.
  7. Let targetModule be binding.[[Module]].
  8. Assert: targetModule is not undefined.
  9. If binding.[[BindingName]] is namespace, then
    1. Return GetModuleNamespace(targetModule, evaluation).
    2. NOTE: The phase here is always evaluation because in import defer * as x from "..."; export { x }, binding.[[BindingName]] is "x" and not namespace.
  10. Let targetEnv be targetModule.[[Environment]].
  11. If targetEnv is empty, throw a ReferenceError exception.
  12. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true).
Note

ResolveExport is side-effect free. Each time this operation is called with a specific exportName, resolveSet pair as arguments it must return the same result. An implementation might choose to pre-compute or cache the ResolveExport results for the [[Exports]] of each module namespace exotic object.

10.4.6.9 [[Set]] ( P, V, Receiver )

The [[Set]] internal method of a module namespace exotic object takes arguments P (a property key), V (an ECMAScript language value), and Receiver (an ECMAScript language value) and returns a normal completion containing false. It performs the following steps when called:

  1. Return false.

10.4.6.10 [[Delete]] ( P )

The [[Delete]] internal method of a module namespace exotic object O takes argument P (a property key) and returns a normal completion containing a Boolean. It performs the following steps when called:

  1. If P is a SymbolIsSymbolLikeNamespaceKey(P, O), return ! OrdinaryDelete(O, P).
  2. Let exports be O.[[Exports]]GetModuleExportsList(O).
  3. If exports contains P, return false.
  4. Return true.

10.4.6.11 [[OwnPropertyKeys]] ( )

The [[OwnPropertyKeys]] internal method of a module namespace exotic object O takes no arguments and returns a normal completion containing a List of property keys. It performs the following steps when called:

  1. Let exports be O.[[Exports]]GetModuleExportsList(O).
  2. If O.[[Deferred]] is true, and exports contains "then", then
    1. Set exports to a copy of exports.
    2. Remove "then" from exports.
  3. Let symbolKeys be OrdinaryOwnPropertyKeys(O).
  4. Return the list-concatenation of exports and symbolKeys.

10.4.6.12 ModuleNamespaceCreate ( module, exports, phase )

The abstract operation ModuleNamespaceCreate takes arguments module (a Module Record), exports (a List of Strings), and phase (defer or evaluation) and returns a module namespace exotic object. It is used to specify the creation of new module namespace exotic objects. It performs the following steps when called:

  1. Assert: module.[[Namespace]] is empty.
  2. Let internalSlotsList be the internal slots listed in Table 1.
  3. Let M be MakeBasicObject(internalSlotsList).
  4. Set M's essential internal methods to the definitions specified in 10.4.6.
  5. Set M.[[Module]] to module.
  6. Let sortedExports be a List whose elements are the elements of exports ordered as if an Array of the same values had been sorted using %Array.prototype.sort% using undefined as comparefn.
  7. Set M.[[Exports]] to sortedExports.
  8. Create own properties of M corresponding to the definitions in 28.3.
  9. If phase is defer, then
    1. Assert: module.[[DeferredNamespace]] is empty.
    2. Set module.[[DeferredNamespace]] to M.
    3. Set M.[[Deferred]] to true.
    4. Let toStringTag be "Deferred Module".
  10. Else,
    1. Assert: module.[[Namespace]] is empty.
    2. Set module.[[Namespace]] to M.
    3. Set M.[[Deferred]] to false.
    4. Let toStringTag be "Module".
  11. Create an own data property of M named %Symbol.toStringTag% whose [[Value]] is toStringTag and whose [[Writable]], [[Enumerable]], and [[Configurable]] attributes are false.
  12. Return M.

10.4.6.13 IsSymbolLikeNamespaceKey ( P, ns )

The abstract operation IsSymbolLikeNamespaceKey takes arguments P (a property key) and ns (a module namespace exotic object) and returns a Boolean. It determines if a property of ns with key P would be an ordinary property, rather than being mapped to one of the module exports. It performs the following steps when called:

  1. If P is a Symbol, return true.
  2. If ns.[[Deferred]] is true and P is "then", return true.
  3. Return false.

10.4.6.14 GetModuleExportsList ( O )

The abstract operation GetModuleExportsList takes argument O (a module namespace exotic object) and returns either a normal completion containing a List of Strings, or a throw completion. It returns a List whose elements are the names of the module's exports, triggering module evaluation if needed. It performs the following steps when called:

  1. If O.[[Deferred]] is true, then
    1. Let m be O.[[Module]].
    2. If m is a Cyclic Module Record, m.[[Status]] is not evaluated, and ReadyForSyncExecution(m) is false, throw a TypeError exception.
    3. Perform ? EvaluateSync(m).
  2. Return O.[[Exports]].

10.4.6.14.1 ReadyForSyncExecution ( module [ , seen ] )

The abstract operation ReadyForSyncExecution takes argument module (a Cyclic Module Record) and optional argument seen (a List of Module Records) and returns a Boolean. It performs the following steps when called:

  1. If seen is not present, set seen to a new empty List.
  2. If seen contains module, return true.
  3. Append module to seen.
  4. If module.[[Status]] is evaluated, return true.
  5. If module.[[Status]] is evaluating or evaluating-async, return false.
  6. Assert: module.[[Status]] is linked.
  7. If module.[[HasTLA]] is true, return false.
  8. For each ModuleRequest Record request of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, request).
    2. If ReadyForSyncExecution(requiredModule, seen) is false, then
      1. Return false.
  9. Return true.

13 ECMAScript Language: Expressions

13.3 Left-Hand-Side Expressions

Syntax

ImportCall[Yield, Await] : import ( AssignmentExpression[+In, ?Yield, ?Await] ,opt ) import ( AssignmentExpression[+In, ?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await] ,opt ) import ImportCallArguments[?Yield, ?Await] import . defer ImportCallArguments[?Yield, ?Await] ImportCallArguments[Yield, Await] : ( AssignmentExpression[+In, ?Yield, ?Await] ,opt ) ( AssignmentExpression[+In, ?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await] ,opt )

13.3.10 Import Calls

Editor's Note
The changes to this section are intended to mirror the changes proposed by the Source Phase Imports proposal, but adapted to the defer phase.

13.3.10.1 Runtime Semantics: Evaluation

ImportCall : import ( AssignmentExpression ,opt )
  1. Return ? EvaluateImportCall(AssignmentExpression).
ImportCall : import ( AssignmentExpression , AssignmentExpression ,opt )
  1. Return ? EvaluateImportCall(the first AssignmentExpression, the second AssignmentExpression).
ImportCall : import ImportCallArguments
  1. Return ? EvaluateImportCall(ImportCallArguments, evaluation).
ImportCall : import . defer ImportCallArguments
  1. Return ? EvaluateImportCall(ImportCallArguments, defer).

13.3.10.2 Runtime Semantics: EvaluateImportCallSpecifier

ImportCallArguments : ( AssignmentExpression ,opt )
  1. Let ref be ? Evaluation of AssignmentExpression.
  2. Return ? GetValue(ref).
ImportCallArguments : ( AssignmentExpression , AssignmentExpression ,opt )
  1. Let ref be ? Evaluation of the first AssignmentExpression.
  2. Return ? GetValue(ref).

13.3.10.3 Runtime Semantics: EvaluateImportCallOptions

ImportCallArguments : ( AssignmentExpression ,opt )
  1. Return undefined.
ImportCallArguments : ( AssignmentExpression , AssignmentExpression ,opt )
  1. Let ref be ? Evaluation of the second AssignmentExpression.
  2. Return ? GetValue(ref).

13.3.10.4 EvaluateImportCall ( arguments, phase, specifierExpression [ , optionsExpression ] )

The abstract operation EvaluateImportCall takes arguments arguments (a Parse Node), phase (defer or evaluation), and specifierExpression (a Parse Node) and optional argument optionsExpression (a Parse Node) and returns either a normal completion containing a Promise or an abrupt completion. It performs the following steps when called:

  1. Let referrer be GetActiveScriptOrModule().
  2. If referrer is null, set referrer to the current Realm Record.
  3. Let specifierRef be ? Evaluation of specifierExpression.
  4. Let specifier be ? GetValue(specifierRef)EvaluateImportCallSpecifier of arguments.
  5. If optionsExpression is present, then
    1. Let optionsRef be ? Evaluation of optionsExpression.
    2. Let options be ? GetValue(optionsRef).
  6. Else,
    1. Let options be undefined.
  7. Let options be ? EvaluateImportCallOptions of arguments.
  8. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  9. Let specifierString be Completion(ToString(specifier)).
  10. IfAbruptRejectPromise(specifierString, promiseCapability).
  11. Let attributes be a new empty List.
  12. If options is not undefined, then
    1. If options is not an Object, then
      1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
      2. Return promiseCapability.[[Promise]].
    2. Let attributesObj be Completion(Get(options, "with")).
    3. IfAbruptRejectPromise(attributesObj, promiseCapability).
    4. If attributesObj is not undefined, then
      1. If attributesObj is not an Object, then
        1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
        2. Return promiseCapability.[[Promise]].
      2. Let entries be Completion(EnumerableOwnProperties(attributesObj, key+value)).
      3. IfAbruptRejectPromise(entries, promiseCapability).
      4. For each element entry of entries, do
        1. Let key be ! Get(entry, "0").
        2. Let value be ! Get(entry, "1").
        3. If key is a String, then
          1. If value is not a String, then
            1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
            2. Return promiseCapability.[[Promise]].
          2. Append the ImportAttribute Record { [[Key]]: key, [[Value]]: value } to attributes.
    5. If AllImportAttributesSupported(attributes) is false, then
      1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
      2. Return promiseCapability.[[Promise]].
    6. Sort attributes according to the lexicographic order of their [[Key]] field, treating the value of each such field as a sequence of UTF-16 code unit values. NOTE: This sorting is observable only in that hosts are prohibited from changing behaviour based on the order in which attributes are enumerated.
  13. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Attributes]]: attributes }.
  14. Let payload be a new DynamicImportState Record { [[PromiseCapability]]: promiseCapability, [[Phase]]: phase }.
  15. Perform HostLoadImportedModule(referrer, moduleRequest, empty, promiseCapabilitypayload).
  16. Return promiseCapability.[[Promise]].

A DynamicImportState Record is a Record that contains information about a dynamic import call. It's used to continue loading after a call to HostLoadImportedModule. Each DynamicImportState Record has the fields defined in Table 2:

Table 2: DynamicImportState Record Fields
Field Name Value Type Meaning
[[PromiseCapability]] a PromiseCapability Record The promise to resolve when the loading process finishes.
[[Phase]] defer or evaluation The target import phase

13.3.10.4.1 ContinueDynamicImport ( promiseCapability, payload, moduleCompletion )

The abstract operation ContinueDynamicImport takes arguments promiseCapability (a PromiseCapability Record), payload (a DynamicImportState Record), and moduleCompletion (either a normal completion containing a Module Record or a throw completion) and returns unused. It completes the process of a dynamic import originally started by an import() call, resolving or rejecting the promise returned by that call as appropriate. It performs the following steps when called:

  1. Let promiseCapability be payload.[[PromiseCapability]].
  2. Let phase be payload.[[Phase]].
  3. If moduleCompletion is an abrupt completion, then
    1. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »).
    2. Return UNUSED.
  4. Let module be moduleCompletion.[[Value]].
  5. Let loadPromise be module.LoadRequestedModules().
  6. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called:
    1. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
    2. Return UNUSED.
  7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
  8. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, phase and onRejected and performs the following steps when called:
    1. Let link be Completion(module.Link()).
    2. If link is an abrupt completion, then
      1. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »).
      2. Return UNUSED.
    3. Let evaluatePromise be module.Evaluate().
    4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module, phase, and promiseCapability and performs the following steps when called:
      1. Let namespace be GetModuleNamespace(module, phase).
      2. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »).
      3. Return UNUSED.
    5. If phase is defer, then
      1. Let evaluationList be GatherAsynchronousTransitiveDependencies(module).
      2. If evaluationList is empty, then
        1. Perform fulfilledClosure().
        2. Return UNUSED.
      3. Let asyncDepsEvaluationPromises be a new empty List.
      4. For each Module Record dep of evaluationList, append dep.Evaluate() to asyncDepsEvaluationPromises.
      5. Let iterator be CreateListIteratorRecord(asyncDepsEvaluationPromises).
      6. Let pc be ! NewPromiseCapability(%Promise%).
      7. Let evaluatePromise be ! PerformPromiseAll(iterator, %Promise%, pc, %Promise.resolve%).
    6. Else,
      1. Assert: phase is evaluation.
      2. Let evaluatePromise be module.Evaluate().
    7. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, "", 0, « »).
    8. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected).
    9. Return UNUSED.
  9. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, "", 0, « »).
  10. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected).
  11. Return UNUSED.

16 ECMAScript Language: Scripts and Modules

16.2 Modules

Syntax

Module : ModuleBodyopt ModuleBody : ModuleItemList ModuleItemList : ModuleItem ModuleItemList ModuleItem ModuleItem : ImportDeclaration ExportDeclaration StatementListItem[~Yield, +Await, ~Return] ModuleExportName : IdentifierName StringLiteral

16.2.1 Module Semantics

16.2.1.1 ModuleRequest Records

A ModuleRequest Record represents the request to import a module with given import attributes and import phase. It consists of the following fields:

Table 3: ModuleRequest Record Fields
Field Name Value Type Meaning
[[Specifier]] a String The module specifier
[[Attributes]] a List of ImportAttribute Records The import attributes
[[Phase]] defer or evaluation The target import phase

16.2.1.1.1 ModuleRequestsEqual ( left, right )

The abstract operation ModuleRequestsEqual takes arguments left (a ModuleRequest Record or a LoadedModuleRequest Record) and right (a ModuleRequest Record or a LoadedModuleRequest Record) and returns a Boolean. ‍It checks whether the two ModuleRequest Records have the same specifier and attributes. It performs the following steps when called:

  1. If left.[[Specifier]] is not right.[[Specifier]], return false.
  2. Let leftAttrs be left.[[Attributes]].
  3. Let rightAttrs be right.[[Attributes]].
  4. Let leftAttrsCount be the number of elements in leftAttrs.
  5. Let rightAttrsCount be the number of elements in rightAttrs.
  6. If leftAttrsCountrightAttrsCount, return false.
  7. For each ImportAttribute Record l of leftAttrs, do
    1. If rightAttrs does not contain an ImportAttribute Record r such that l.[[Key]] is r.[[Key]] and l.[[Value]] is r.[[Value]], return false.
  8. Return true.

16.2.1.3 Static Semantics: ModuleRequests

The syntax-directed operation ModuleRequests takes no arguments and returns a List of ModuleRequest Records. It is defined piecewise over the following productions:

Module : [empty]
  1. Return a new empty List.
ModuleItemList : ModuleItem
  1. Return ModuleRequests of ModuleItem.
ModuleItemList : ModuleItemList ModuleItem
  1. Let requests be the ModuleRequests of ModuleItemList.
  2. Let additionalRequests be the ModuleRequests of ModuleItem.
  3. For each ModuleRequest Record mr of additionalRequests, do
    1. If requests does not contain a ModuleRequest Record mr2 such that ModuleRequestsEqual(mr, mr2) is true and mr.[[Phase]] is mr2.[[Phase]], then
      1. Append mr to requests.
  4. Return requests.
ModuleItem : StatementListItem
  1. Return a new empty List.
ImportDeclaration : import ImportClause FromClause ;
  1. Let specifier be the SV of FromClause.
  2. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: « », [[Phase]]: evaluation }.
ImportDeclaration : import ImportClause FromClause WithClause ;
  1. Let specifier be the SV of FromClause.
  2. Let attributes be WithClauseToAttributes of WithClause.
  3. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: attributes, [[Phase]]: evaluation }.
ImportDeclaration : import defer NameSpaceImport FromClause ;
  1. Let specifier be SV of FromClause.
  2. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: « », [[Phase]]: defer }.
ImportDeclaration : import defer NameSpaceImport FromClause WithClause ;
  1. Let specifier be SV of FromClause.
  2. Let attributes be WithClauseToAttributes of WithClause.
  3. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: attributes, [[Phase]]: defer }.
ModuleSpecifier : StringLiteral
  1. Return a List whose sole element is the SV of StringLiteral.
ExportDeclaration : export ExportFromClause FromClause ;
  1. Let specifier be SV of FromClause.
  2. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: « », [[Phase]]: evaluation }.
ExportDeclaration : export ExportFromClause FromClause WithClause ;
  1. Let specifier be the SV of FromClause.
  2. Let attributes be WithClauseToAttributes of WithClause.
  3. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: attributes, [[Phase]]: evaluation }.
ExportDeclaration : export NamedExports ; export VariableStatement export Declaration export default HoistableDeclaration export default ClassDeclaration export default AssignmentExpression ;
  1. Return a new empty List.

16.2.1.4 Abstract Module Records

A Module Record encapsulates structural information about the imports and exports of a single module. This information is used to link the imports and exports of sets of connected modules. A Module Record includes four fields that are only used when evaluating a module.

For specification purposes Module Record values are values of the Record specification type and can be thought of as existing in a simple object-oriented hierarchy where Module Record is an abstract class with both abstract and concrete subclasses. This specification defines the abstract subclass named Cyclic Module Record and its concrete subclass named Source Text Module Record. Other specifications and implementations may define additional Module Record subclasses corresponding to alternative module definition facilities that they defined.

Module Record defines the fields listed in Table 4. All Module Definition subclasses include at least those fields. Module Record also defines the abstract method list in Table 44. All Module definition subclasses must provide concrete implementations of these abstract methods.

Table 4: Module Record Fields
Field Name Value Type Meaning
[[Realm]] a Realm Record The Realm within which this module was created.
[[Environment]] a Module Environment Record or empty The Environment Record containing the top level bindings for this module. This field is set when the module is linked.
[[Namespace]] an Object or empty The Module Namespace Object (10.4.6) whose [[Deferred]] slot is false, if one has been created for this module.
[[DeferredNamespace]] an Object or empty The Module Namespace Object (10.4.6) whose [[Deferred]] slot is true, if one has been requested for this module.
[[HostDefined]] anything (default value is undefined) Field reserved for use by host environments that need to associate additional information with a module.

16.2.1.5 Cyclic Module Records

A Cyclic Module Record is used to represent information about a module that can participate in dependency cycles with other modules that are subclasses of the Cyclic Module Record type. Module Records that are not subclasses of the Cyclic Module Record type must not participate in dependency cycles with Source Text Module Records.

16.2.1.5.1 LoadRequestedModules ( [ hostDefined ] )

The LoadRequestedModules concrete method of a Cyclic Module Record module takes optional argument hostDefined (anything) and returns a Promise.

16.2.1.5.1.1 InnerModuleLoading ( state, module )

The abstract operation InnerModuleLoading takes arguments state (a GraphLoadingState Record) and module (a Module Record) and returns unused.

Note
There is no special handling for ModuleRequests whose [[Phase]] is defer: any error from loading or parsing the corresponding modules will make InnerModuleLoading fail.

16.2.1.5.2 Link ( )

The Link concrete method of a Cyclic Module Record module takes no arguments and returns either a normal completion containing unused or a throw completion.

16.2.1.5.2.1 InnerModuleLinking ( module, stack, index )

The abstract operation InnerModuleLinking takes arguments module (a Module Record), stack (a List of Cyclic Module Records), and index (a non-negative integer) and returns either a normal completion containing a non-negative integer or a throw completion.

Note
There is no special handling for ModuleRequests whose [[Phase]] is defer: any error from linking the corresponding modules will make InnerModuleLinking fail.

16.2.1.5.3 Evaluate ( )

The Evaluate concrete method of a Cyclic Module Record module takes no arguments and returns a Promise. Evaluate transitions this module's [[Status]] from linked to either evaluating-async or evaluated. The first time it is called on a module in a given strongly connected component, Evaluate creates and returns a Promise which resolves when the module has finished evaluating. This Promise is stored in the [[TopLevelCapability]] field of the [[CycleRoot]] for the component. Future invocations of Evaluate on any module in the component return the same Promise. (Most of the work is done by the auxiliary function InnerModuleEvaluation.) It performs the following steps when called:

  1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent..
  2. Assert: None of module or any of its recursive dependencies have [[Status]] set to evaluating, linking, unlinked, or new.
  3. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated.
  4. If module.[[Status]] is either evaluating-async or evaluated, set module to module.[[CycleRoot]].
  5. If module.[[TopLevelCapability]] is not empty, then
    1. Return module.[[TopLevelCapability]].[[Promise]].
  6. Let stack be a new empty List.
  7. Let capability be ! NewPromiseCapability(%Promise%).
  8. Set module.[[TopLevelCapability]] to capability.
  9. Let result be Completion(InnerModuleEvaluation(module, stack, 0)).
  10. If result is an abrupt completion, then
    1. For each Cyclic Module Record m of stack, do
      1. Assert: m.[[Status]] is evaluating.
      2. Set m.[[Status]] to evaluated.
      3. Set m.[[EvaluationError]] to result.
    2. Assert: module.[[Status]] is evaluated.
    3. Assert: module.[[EvaluationError]] is result.
    4. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »).
  11. Else,
    1. Assert: module.[[Status]] is either evaluating-async or evaluated.
    2. Assert: module.[[EvaluationError]] is empty.
    3. If module.[[AsyncEvaluation]] is false, then
      1. Assert: module.[[Status]] is evaluated.
      2. Perform ! Call(capability.[[Resolve]], undefined, « undefined »).
    4. Assert: stack is empty.
  12. Return capability.[[Promise]].

16.2.1.5.3.1 InnerModuleEvaluation ( module, stack, index )

The abstract operation InnerModuleEvaluation takes arguments module (a Module Record), stack (a List of Cyclic Module Records), and index (a non-negative integer) and returns either a normal completion containing a non-negative integer or a throw completion. It is used by Evaluate to perform the actual evaluation process for module, as well as recursively on all other modules in the dependency graph. The stack and index parameters, as well as module's [[DFSIndex]] and [[DFSAncestorIndex]] fields, are used the same way as in InnerModuleLinking. It performs the following steps when called:

  1. If module is not a Cyclic Module Record, then
    1. Let promise be ! module.Evaluate().
    2. Assert: promise.[[PromiseState]] is not pending.
    3. If promise.[[PromiseState]] is rejected, then
      1. Return ThrowCompletion(promise.[[PromiseResult]]).
    4. Perform ? EvaluateSync(module).
    5. Return index.
  2. If module.[[Status]] is either evaluating-async or evaluated, then
    1. If module.[[EvaluationError]] is empty, return index.
    2. Otherwise, return ? module.[[EvaluationError]].
  3. If module.[[Status]] is evaluating, return index.
  4. Assert: module.[[Status]] is linked.
  5. Set module.[[Status]] to evaluating.
  6. Set module.[[DFSIndex]] to index.
  7. Set module.[[DFSAncestorIndex]] to index.
  8. Set module.[[PendingAsyncDependencies]] to 0.
  9. Set index to index + 1.
  10. Let evaluationList be a new empty List.
  11. For each ModuleRequest Record request of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, request).
    2. If request.[[Phase]] is defer, then
      1. Let additionalModules be GatherAsynchronousTransitiveDependencies(requiredModule).
      2. For each Module Record additionalModule of additionalModules, do
        1. If evaluationList does not contain additionalModule, then
          1. Append additionalModule to evaluationList.
    3. Else if evaluationList does not contain requiredModule, then
      1. Append requiredModule to evaluationList.
  12. Append module to stack.
  13. For each ModuleRequest Record request of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, request).
  14. For each Module Record requiredModule of evaluationList, do
    1. Set index to ? InnerModuleEvaluation(requiredModule, stack, index).
    2. If requiredModule is a Cyclic Module Record, then
      1. Assert: requiredModule.[[Status]] is one of evaluating, evaluating-async, or evaluated.
      2. Assert: requiredModule.[[Status]] is evaluating if and only if stack contains requiredModule.
      3. If requiredModule.[[Status]] is evaluating, then
        1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]).
      4. Else,
        1. Set requiredModule to requiredModule.[[CycleRoot]].
        2. Assert: requiredModule.[[Status]] is either evaluating-async or evaluated.
        3. If requiredModule.[[EvaluationError]] is not empty, return ? requiredModule.[[EvaluationError]].
      5. If requiredModule.[[AsyncEvaluation]] is true, then
        1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1.
        2. Append module to requiredModule.[[AsyncParentModules]].
  15. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then
    1. Assert: module.[[AsyncEvaluation]] is false.
    2. Set module.[[AsyncEvaluation]] to true.
    3. NOTE: The order in which module records have their [[AsyncEvaluation]] fields transition to true is significant. (See 16.2.1.6.3.4.)
    4. If module.[[PendingAsyncDependencies]] = 0, perform ExecuteAsyncModule(module).
  16. Otherwise, perform ? module.ExecuteModule().
  17. Assert: module occurs exactly once in stack.
  18. Assert: module.[[DFSAncestorIndex]]module.[[DFSIndex]].
  19. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then
    1. Let done be false.
    2. Repeat, while done is false,
      1. Let requiredModule be the last element of stack.
      2. Remove the last element of stack.
      3. Assert: requiredModule is a Cyclic Module Record.
      4. If requiredModule.[[AsyncEvaluation]] is false, then
        1. Set requiredModule.[[Status]] to evaluated.
      5. Else,
        1. Set requiredModule.[[Status]] to evaluating-async.
      6. If requiredModule and module are the same Module Record, set done to true.
      7. Set requiredModule.[[CycleRoot]] to module.
  20. Return index.
Note 1

A module is evaluating while it is being traversed by InnerModuleEvaluation. A module is evaluated on execution completion or evaluating-async during execution if its [[HasTLA]] field is true or if it has asynchronous dependencies.

Note 2

Any modules depending on a module of an asynchronous cycle when that cycle is not evaluating will instead depend on the execution of the root of the cycle via [[CycleRoot]]. This ensures that the cycle state can be treated as a single strongly connected component through its root module state.

16.2.1.5.3.2 EvaluateSync ( module )

The abstract operation EvaluateSync takes argument module (a Module Record) and returns either a normal completion containing unused or a throw completion. It synchronously evaluates module provided it is ready for synchronous execution. It performs the following steps when called:

  1. Assert: If module is a Cyclic Module Record, module.[[HasTLA]] is false.
  2. Assert: For each Cyclic Module Record m that it a transitive dependency of module, either m.[[HasTLA]] is false or m.[[Status]] is evaluated.
  3. Let promise be ! module.Evaluate().
  4. Assert: promise.[[PromiseState]] is either fulfilled or rejected.
  5. If promise.[[PromiseState]] is rejected, then
    1. Throw promise.[[PromiseResult]].
  6. Return unused.

16.2.1.5.3.3 GatherAsynchronousTransitiveDependencies ( module [ , seen ] )

The abstract operation GatherAsynchronousTransitiveDependencies takes argument module (a Module Record) and optional argument seen (a List of Module Records) and returns a List of Module Records. Collects the direct post-order list of asynchronous unexecuted transitive dependencies, stopping the depth-first search for a branch when an asynchronous dependency is found. It performs the following steps when called:

  1. If seen is not present, set seen to a new empty List.
  2. Let result be a new empty List.
  3. If seen contains module, return result.
  4. Append module to seen.
  5. If module is not a Cyclic Module Record, return result.
  6. If module.[[Status]] is either evaluating or evaluated, return result.
  7. If module.[[HasTLA]] is true, then
    1. Append module to result.
    2. Return result.
  8. For each ModuleRequest Record request of module.[[RequestedModules]], do
    1. Let requiredModule be GetImportedModule(module, request).
    2. Let additionalModules be GatherAsynchronousTransitiveDependencies(requiredModule, seen).
    3. For each Module Record m of additionalModules, do
      1. If result does not contain m, append m to result.
  9. Return result.

16.2.1.5.4 Example Cyclic Module Record Graphs

This non-normative section gives a series of examples of the linking and evaluation of a few common module graphs, with a specific focus on how errors can occur.

First consider the following simple module graph:

Figure 1: A simple module graph
A module graph in which module A depends on module B, and module B depends on module C

Let's first assume that there are no error conditions. When a host first calls A.LoadRequestedModules(), this will complete successfully by assumption, and recursively load the dependencies of B and C as well (respectively, C and none), and then set A.[[Status]] = B.[[Status]] = C.[[Status]] = unlinked. Then, when the host calls A.Link(), it will complete successfully (again by assumption) such that A.[[Status]] = B.[[Status]] = C.[[Status]] = linked. These preparatory steps can be performed at any time. Later, when the host is ready to incur any possible side effects of the modules, it can call A.Evaluate(), which will complete successfully, returning a Promise resolving to undefined (again by assumption), recursively having evaluated first C and then B. Each module's [[Status]] at this point will be evaluated.

Consider then cases involving linking errors, after a successful call to A.LoadRequestedModules(). If InnerModuleLinking of C succeeds but, thereafter, fails for B, for example because it imports something that C does not provide, then the original A.Link() will fail, and both A and B's [[Status]] remain unlinked. C's [[Status]] has become linked, though.

Finally, consider a case involving evaluation errors after a successful call to Link(). If InnerModuleEvaluation of C succeeds but, thereafter, fails for B, for example because B contains code that throws an exception, then the original A.Evaluate() will fail, returning a rejected Promise. The resulting exception will be recorded in both A and B's [[EvaluationError]] fields, and their [[Status]] will become evaluated. C will also become evaluated but, in contrast to A and B, will remain without an [[EvaluationError]], as it successfully completed evaluation. Storing the exception ensures that any time a host tries to reuse A or B by calling their Evaluate() method, it will encounter the same exception. (Hosts are not required to reuse Cyclic Module Records; similarly, hosts are not required to expose the exception objects thrown by these methods. However, the specification enables such uses.)

Now consider a different type of error condition:

Figure 2: A module graph with an unresolvable module
A module graph in which module A depends on a missing (unresolvable) module, represented by ???

In this scenario, module A declares a dependency on some other module, but no Module Record exists for that module, i.e. HostLoadImportedModule calls FinishLoadingImportedModule with an exception when asked for it. This could occur for a variety of reasons, such as the corresponding resource not existing, or the resource existing but ParseModule returning some errors when trying to parse the resulting source text. Hosts can choose to expose the cause of failure via the completion they pass to FinishLoadingImportedModule. In any case, this exception causes a loading failure, which results in A's [[Status]] remaining new.

The difference here between loading, linking and evaluation errors is due to the following characteristic:

  • Evaluation must be only performed once, as it can cause side effects; it is thus important to remember whether evaluation has already been performed, even if unsuccessfully. (In the error case, it makes sense to also remember the exception because otherwise subsequent Evaluate() calls would have to synthesize a new one.)
  • Linking, on the other hand, is side-effect-free, and thus even if it fails, it can be retried at a later time with no issues.
  • Loading closely interacts with the host, and it may be desiderable for some of them to allow users to retry failed loads (for example, if the failure is caused by temporarily bad network conditions).

Now, consider a module graph with a cycle:

Figure 3: A cyclic module graph
A module graph in which module A depends on module B and C, but module B also depends on module A

Here we assume that the entry point is module A, so that the host proceeds by calling A.LoadRequestedModules(), which performs InnerModuleLoading on A. This in turn calls InnerModuleLoading on B and C. Because of the cycle, this again triggers InnerModuleLoading on A, but at this point it is a no-op since A's dependencies loading has already been triggered during this LoadRequestedModules process. When all the modules in the graph have been successfully loaded, their [[Status]] transitions from new to unlinked at the same time.

Then the host proceeds by calling A.Link(), which performs InnerModuleLinking on A. This in turn calls InnerModuleLinking on B. Because of the cycle, this again triggers InnerModuleLinking on A, but at this point it is a no-op since A.[[Status]] is already linking. B.[[Status]] itself remains linking when control gets back to A and InnerModuleLinking is triggered on C. After this returns with C.[[Status]] being linked, both A and B transition from linking to linked together; this is by design, since they form a strongly connected component. It's possible to transition the status of modules in the same SCC at the same time because during this phase the module graph is traversed with a depth-first search.

An analogous story occurs for the evaluation phase of a cyclic module graph, in the success case.

Now consider a case where A has a linking error; for example, it tries to import a binding from C that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleLinking on A. However, once we unwind back to the original InnerModuleLinking on A, it fails during InitializeEnvironment, namely right after C.ResolveExport(). The thrown SyntaxError exception propagates up to A.Link, which resets all modules that are currently on its stack (these are always exactly the modules that are still linking). Hence both A and B become unlinked. Note that C is left as linked.

Alternatively, consider a case where A has an evaluation error; for example, its source code throws an exception. In that case, the evaluation-time analog of the above steps still occurs, including the early return from the second call to InnerModuleEvaluation on A. However, once we unwind back to the original InnerModuleEvaluation on A, it fails by assumption. The exception thrown propagates up to A.Evaluate(), which records the error in all modules that are currently on its stack (i.e., the modules that are still evaluating) as well as via [[AsyncParentModules]], which form a chain for modules which contain or depend on top-level await through the whole dependency graph through the AsyncModuleExecutionRejected algorithm. Hence both A and B become evaluated and the exception is recorded in both A and B's [[EvaluationError]] fields, while C is left as evaluated with no [[EvaluationError]].

Lastly, consider a module graph with a cycle, where all modules complete asynchronously:

Figure 4: An asynchronous cyclic module graph
A module graph in which module A depends on module B and C, module B depends on module D, module C depends on module D and E, and module D depends on module A

Loading and linking happen as before, and all modules end up with [[Status]] set to linked.

Calling A.Evaluate() calls InnerModuleEvaluation on A, B, and D, which all transition to evaluating. Then InnerModuleEvaluation is called on A again, which is a no-op because it is already evaluating. At this point, D.[[PendingAsyncDependencies]] is 0, so ExecuteAsyncModule(D) is called and we call D.ExecuteModule with a new PromiseCapability tracking the asynchronous execution of D. We unwind back to the InnerModuleEvaluation on B, setting B.[[PendingAsyncDependencies]] to 1 and B.[[AsyncEvaluation]] to true. We unwind back to the original InnerModuleEvaluation on A, setting A.[[PendingAsyncDependencies]] to 1. In the next iteration of the loop over A's dependencies, we call InnerModuleEvaluation on C and thus on D (again a no-op) and E. As E has no dependencies and is not part of a cycle, we call ExecuteAsyncModule(E) in the same manner as D and E is immediately removed from the stack. We unwind once more to the original InnerModuleEvaluation on A, setting C.[[AsyncEvaluation]] to true. Now we finish the loop over A's dependencies, set A.[[AsyncEvaluation]] to true, and remove the entire strongly connected component from the stack, transitioning all of the modules to evaluating-async at once. At this point, the fields of the modules are as given in Table 5.

Table 5: Module fields after the initial Evaluate() call
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluating-async true « » 2 (B and C)
B 1 0 evaluating-async true « A » 1 (D)
C 2 0 evaluating-async true « A » 2 (D and E)
D 3 0 evaluating-async true « B, C » 0
E 4 4 evaluating-async true « C » 0

Let us assume that E finishes executing first. When that happens, AsyncModuleExecutionFulfilled is called, E.[[Status]] is set to evaluated and C.[[PendingAsyncDependencies]] is decremented to become 1. The fields of the updated modules are as given in Table 6.

Table 6: Module fields after module E finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
C 2 0 evaluating-async true « A » 1 (D)
E 4 4 evaluated true « C » 0

D is next to finish (as it was the only module that was still executing). When that happens, AsyncModuleExecutionFulfilled is called again and D.[[Status]] is set to evaluated. Then B.[[PendingAsyncDependencies]] is decremented to become 0, ExecuteAsyncModule is called on B, and it starts executing. C.[[PendingAsyncDependencies]] is also decremented to become 0, and C starts executing (potentially in parallel to B if B contains an await). The fields of the updated modules are as given in Table 7.

Table 7: Module fields after module D finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
B 1 0 evaluating-async true « A » 0
C 2 0 evaluating-async true « A » 0
D 3 0 evaluated true « B, C » 0

Let us assume that C finishes executing next. When that happens, AsyncModuleExecutionFulfilled is called again, C.[[Status]] is set to evaluated and A.[[PendingAsyncDependencies]] is decremented to become 1. The fields of the updated modules are as given in Table 8.

Table 8: Module fields after module C finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluating-async true « » 1 (B)
C 2 0 evaluated true « A » 0

Then, B finishes executing. When that happens, AsyncModuleExecutionFulfilled is called again and B.[[Status]] is set to evaluated. A.[[PendingAsyncDependencies]] is decremented to become 0, so ExecuteAsyncModule is called and it starts executing. The fields of the updated modules are as given in Table 9.

Table 9: Module fields after module B finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluating-async true « » 0
B 1 0 evaluated true « A » 0

Finally, A finishes executing. When that happens, AsyncModuleExecutionFulfilled is called again and A.[[Status]] is set to evaluated. At this point, the Promise in A.[[TopLevelCapability]] (which was returned from A.Evaluate()) is resolved, and this concludes the handling of this module graph. The fields of the updated module are as given in Table 10.

Table 10: Module fields after module A finishes executing
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A 0 0 evaluated true « » 0

Alternatively, consider a failure case where C fails execution and returns an error before B has finished executing. When that happens, AsyncModuleExecutionRejected is called, which sets C.[[Status]] to evaluated and C.[[EvaluationError]] to the error. It then propagates this error to all of the AsyncParentModules by performing AsyncModuleExecutionRejected on each of them. The fields of the updated modules are as given in Table 11.

Table 11: Module fields after module C finishes with an error
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A 0 0 evaluated true « » 1 (B) empty
C 2 1 evaluated true « A » 0 C's evaluation error

A will be rejected with the same error as C since C will call AsyncModuleExecutionRejected on A with C's error. A.[[Status]] is set to evaluated. At this point the Promise in A.[[TopLevelCapability]] (which was returned from A.Evaluate()) is rejected. The fields of the updated module are as given in Table 12.

Table 12: Module fields after module A is rejected
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A 0 0 evaluated true « » 0 C's Evaluation Error

Then, B finishes executing without an error. When that happens, AsyncModuleExecutionFulfilled is called again and B.[[Status]] is set to evaluated. GatherAvailableAncestors is called on B. However, A.[[CycleRoot]] is A which has an evaluation error, so it will not be added to the returned sortedExecList and AsyncModuleExecutionFulfilled will return without further processing. Any future importer of B will resolve the rejection of B.[[CycleRoot]].[[EvaluationError]] from the evaluation error from C that was set on the cycle root A. The fields of the updated modules are as given in Table 13.

Table 13: Module fields after module B finishes executing in an erroring graph
Module [[DFSIndex]] [[DFSAncestorIndex]] [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A 0 0 evaluated true « » 0 C's Evaluation Error
B 1 0 evaluated true « A » 0 empty

16.2.1.5.4.1 Example Cyclic Module Record Graphs with Deferred Imports

Deferred imports complicate handling of module graphs, because they allow deferring evaluation of part of the graph while still eagerly evaluating the asynchronous subgraphs of a deferred portion. In this section, deferred imports are marked with dashed arrows and modules using top-level await are marked with TLA.

Consider the following graph, assuming that all the modules have already their [[Status]] set to linked:

Figure 5: A simple module graph with a deferred import
A module graph in which module A depends on module B with a deferred import, and module B depends on module C

Calling A.Evaluate() calls InnerModuleEvaluation A, which transitions A.[[Status]] to evaluating. A has a deferred import for B, so it performs GatherAsynchronousTransitiveDependencies(B). B doesn't have any transitive dependency using top-level await, so that AO doesn't collect any module. A also imports D, so the list of modules that A's evaluation depends on is « D »: the InnerModuleEvaluation algorithm run on A performs InnerModuleEvaluation on D, transitioning its [[Status]] to evaluating and then evaluated, and then it calls A.ExecuteModule(). If A's execution triggers B.Evaluate(), then InnerModuleEvaluation will be called on B and C executing them and transitioning their [[Status]] to evaluated. Finally, A.[[Status]] transitions to evaluated.

Consider the same graph, but with C using top-level await:

Figure 6: A simple module graph with a deferred import and a transitive asynchronous dependency
A module graph in which module A depends on module B with a deferred import, and module B depends on module C, which uses top-level await.

Calling A.Evaluate() calls InnerModuleEvaluation A, which transitions A.[[Status]] to evaluating. It then performs GatherAsynchronousTransitiveDependencies(B), finding the module C: the list of modules whose evaluation A's evaluation depends on is « C, D ». It then calls InnerModuleEvaluation on C and D, transitioning their [[Status]] respectively to evaluating-async and evaluated and registering A's evaluation as pending on the asynchronous dependency C. At this point, the fields of the modules are given in Table 14:

Table 14: Module fields after the initial Evaluate() call
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A evaluating-async true « » 1 (C)
B linked false « » 0
C evaluating-async true « A » 0
D evaluated false « » 0

Let us assume that C succesfully finishes evaluating. When that happens, AsyncModuleExecutionFulfilled(C) is called, C.[[Status]] is set to evaluated, A.ExecuteModule() is called and upon successful completion A.[[Status]] is set to evaluted. The fields of the modules are now as given in Table 15:

Table 15: Module fields after C finishes executing
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]]
A evaluated true « » 0
B linked false « » 0
C evaluated true « A » 0
D evaluated false « » 0

If later B.Evaluate() is called, then the InnerModuleEvaluation call on B will transition B.[[Status]] to evaluating and will call InnerModuleEvaluation on C. Since at this point C.[[Status]] is already evaluated, B.ExecuteModule() will be called synchronously and B.[[Status]] will synchronously transition to evaluated.

Alternatively, consider a failure case where C fails to execute with an exception error. When that happens, AsyncModuleExecutionRejected(C, error) is called, it sets C.[[Status]] = A.[[Status]] to evaluated and C.[[EvaluationError]] = A.[[EvaluationError]] to ThrowCompletion(error). The fields of the modules are this as given in Table 16:

Table 16: Module fields after C finishes executing with an exception
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[EvaluationError]]
A evaluated true « » 1 (C) error
B linked false « » 0 empty
C evaluated true « B » 0 error
D evaluated false « » 0 empty

If at any later point in time B.Evaluate() is called, it will perform InnerModuleEvaluation on C which immediately returns with ThrowCompletion(error), transitioning B.[[Status]] to evalauted and setting B.[[EvaluationError]] to error.

Consider now a graph with a deferred import taking part in a dependencies cycle, and assume that the evaluation of A calls B.Evaluate():

Figure 7: A module graph with a deferred import and a cycle back to the root
A module graph in which module A depends on module B with a deferred import, module B depends on module C, which uses top-level await and depends on A

As in the previous example, calling A.Evaluate() calls InnerModuleEvaluation on A, GatherAsynchronousTransitiveDependencies on B will find C and thus it performs InnerModuleEvaluation on C, transitioning their A and C's [[Status]] to evaluating-async. When InnerModuleEvaluation iterates over C's dependencies, it finds A and sets A.[[CycleRoot]] = C.[[CycleRoot]] = A. At this point, the fields of the modules are given in Table 17:

Table 17: Module fields after the initial Evaluate() call
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[CycleRoot]]
A evaluating-async true « » 1 (C) A
B linked false « » 0 empty
C evaluating-async true « A » 0 A

Assume now that C succesfully finishes evaluating. When that happens AsyncModuleExecutionFulfilled(C) is called, it sets C.[[Status]] to evaluated, and it performs A.ExecuteModule(). As stated, A's execution causes a call to B.Evaluate(), which calls InnerModuleEvaluation on B and on C. Since C.[[Status]] is evaluated, B.ExecuteModule() is called synchronously and, assuming it completes succesfully, transitions B.[[Status]] to evaluated. At this point, the fields of the modules are given in Table 18:

Table 18: Module fields after the B.ExecuteModule() call
Module [[Status]] [[AsyncEvaluation]] [[AsyncParentModules]] [[PendingAsyncDependencies]] [[CycleRoot]]
A evaluating-async true « » 0 A
B evaluated false « » 0 B
C evaluated true « A » 0 A

Once the call to A.ExecuteModule() completes, A.[[Status]] transitions to evaluated.

16.2.1.6 Source Text Module Records

16.2.1.6.4 InitializeEnvironment ( )

The InitializeEnvironment concrete method of a Source Text Module Record module takes no arguments and returns either a normal completion containing unused or a throw completion. It performs the following steps when called:

  1. For each ExportEntry Record e of module.[[IndirectExportEntries]], do
    1. Let resolution be module.ResolveExport(e.[[ExportName]]).
    2. If resolution is either null or ambiguous, throw a SyntaxError exception.
    3. Assert: resolution is a ResolvedBinding Record.
  2. Assert: All named exports from module are resolvable.
  3. Let realm be module.[[Realm]].
  4. Assert: realm is not undefined.
  5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
  6. Set module.[[Environment]] to env.
  7. For each ImportEntry Record in of module.[[ImportEntries]], do
    1. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]).
    2. If in.[[ImportName]] is namespace-object, then
      1. Let namespace be GetModuleNamespace(importedModule, in.[[ModuleRequest]].[[Phase]]).
      2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
      3. Perform ! env.InitializeBinding(in.[[LocalName]], namespace).
    3. Else,
      1. Let resolution be importedModule.ResolveExport(in.[[ImportName]]).
      2. If resolution is either null or ambiguous, throw a SyntaxError exception.
      3. If resolution.[[BindingName]] is namespace, then
        1. NOTE: In this case we have an export * as ns from ""; declaration. import * as ns from ""; export { ns } indirect re-exports have resolution.[[BindingName]] set to "ns".
        2. Let namespace be GetModuleNamespace(resolution.[[Module]], evaluation).
        3. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
        4. Perform ! env.InitializeBinding(in.[[LocalName]], namespace).
      4. Else,
        1. Perform env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]).
  8. Let moduleContext be a new ECMAScript code execution context.
  9. Set the Function of moduleContext to null.
  10. Assert: module.[[Realm]] is not undefined.
  11. Set the Realm of moduleContext to module.[[Realm]].
  12. Set the ScriptOrModule of moduleContext to module.
  13. Set the VariableEnvironment of moduleContext to module.[[Environment]].
  14. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
  15. Set the PrivateEnvironment of moduleContext to null.
  16. Set module.[[Context]] to moduleContext.
  17. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
  18. Let code be module.[[ECMAScriptCode]].
  19. Let varDeclarations be the VarScopedDeclarations of code.
  20. Let declaredVarNames be a new empty List.
  21. For each element d of varDeclarations, do
    1. For each element dn of the BoundNames of d, do
      1. If declaredVarNames does not contain dn, then
        1. Perform ! env.CreateMutableBinding(dn, false).
        2. Perform ! env.InitializeBinding(dn, undefined).
        3. Append dn to declaredVarNames.
  22. Let lexDeclarations be the LexicallyScopedDeclarations of code.
  23. Let privateEnv be null.
  24. For each element d of lexDeclarations, do
    1. For each element dn of the BoundNames of d, do
      1. If IsConstantDeclaration of d is true, then
        1. Perform ! env.CreateImmutableBinding(dn, true).
      2. Else,
        1. Perform ! env.CreateMutableBinding(dn, false).
      3. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
        1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
        2. Perform ! env.InitializeBinding(dn, fo).
  25. Remove moduleContext from the execution context stack.
  26. Return unused.

16.2.1.7 AllImportAttributesSupported ( attributes )

The abstract operation AllImportAttributesSupported takes argument attributes (a List of ImportAttribute Records) and returns a Boolean. It performs the following steps when called:

  1. Let supported be HostGetSupportedImportAttributes().
  2. For each ImportAttribute Record attribute of attributes, do
    1. If supported does not contain attribute.[[Key]], return false.
  3. Return true.

16.2.1.7.1 HostGetSupportedImportAttributes ( )

The host-defined abstract operation HostGetSupportedImportAttributes takes no arguments and returns a List of Strings. It allows host environments to specify which import attributes they support. Only attributes with supported keys will be provided to the host.

An implementation of HostGetSupportedImportAttributes must conform to the following requrements:

  • It must return a List of Strings, each indicating a supported attribute.
  • Each time this operation is called, it must return the same List with the same contents in the same order.

The default implementation of HostGetSupportedImportAttributes is to return a new empty List.

Note
The purpose of requiring the host to specify its supported import attributes, rather than passing all attributes to the host and letting it then choose which ones it wants to handle, is to ensure that unsupported attributes are handled in a consistent way across different hosts.

16.2.1.10 GetModuleNamespace ( module, phase )

The abstract operation GetModuleNamespace takes arguments module (an instance of a concrete subclass of Module Record) and phase (defer or evaluation) and returns a Module Namespace Object. It retrieves the Module Namespace Object representing module's exports, lazily creating it the first time it was requested, and storing it in module.[[Namespace]] for future retrieval. It performs the following steps when called:

  1. Assert: If module is a Cyclic Module Record, then module.[[Status]] is not new or unlinked.
  2. If phase is defer, let namespace be module.[[DeferredNamespace]]. Otherwise, let namespace be module.[[Namespace]].
  3. If namespace is empty, then
    1. Let exportedNames be module.GetExportedNames().
    2. Let unambiguousNames be a new empty List.
    3. For each element name of exportedNames, do
      1. Let resolution be module.ResolveExport(name).
      2. If resolution is a ResolvedBinding Record, append name to unambiguousNames.
    4. Set namespace to ModuleNamespaceCreate(module, unambiguousNames, phase).
  4. Return namespace.
Note

GetModuleNamespace never throws. Instead, unresolvable names are simply excluded from the namespace at this point. They will lead to a real linking error later unless they are all ambiguous star exports that are not explicitly requested anywhere.

16.2.2 Imports

Syntax

ImportDeclaration : import ImportClause FromClause WithClauseopt ; import defer NameSpaceImport FromClause WithClauseopt ; import ModuleSpecifier WithClauseopt ; ImportClause : ImportedDefaultBinding NameSpaceImport NamedImports ImportedDefaultBinding , NameSpaceImport ImportedDefaultBinding , NamedImports ImportedDefaultBinding : ImportedBinding NameSpaceImport : * as ImportedBinding NamedImports : { } { ImportsList } { ImportsList , } FromClause : from ModuleSpecifier ImportsList : ImportSpecifier ImportsList , ImportSpecifier ImportSpecifier : ImportedBinding ModuleExportName as ImportedBinding ModuleSpecifier : StringLiteral ImportedBinding : BindingIdentifier[~Yield, +Await] WithClause : with { } with { WithEntries ,opt } WithEntries : AttributeKey : StringLiteral AttributeKey : StringLiteral , WithEntries AttributeKey : IdentifierName StringLiteral

16.2.2.1 Static Semantics: Early Errors

ModuleItem : ImportDeclaration

16.2.2.2 Static Semantics: ImportEntries

The syntax-directed operation ImportEntries takes no arguments and returns a List of ImportEntry Records. It is defined piecewise over the following productions:

Module : [empty]
  1. Return a new empty List.
ModuleItemList : ModuleItemList ModuleItem
  1. Let entries1 be ImportEntries of ModuleItemList.
  2. Let entries2 be ImportEntries of ModuleItem.
  3. Return the list-concatenation of entries1 and entries2.
ModuleItem : ExportDeclaration StatementListItem
  1. Return a new empty List.
ImportDeclaration : import ImportClause FromClause WithClauseopt ;
  1. Let module be the sole element of ModuleRequestsSV of FromClause.
  2. Return ImportEntriesForModule of ImportClause with arguments module.
ImportDeclaration : import defer NameSpaceImport FromClause WithClauseopt ;
  1. Let module be SV of FromClause.
  2. Return ImportEntriesForModule of NameSpaceImport with arguments module.
ImportDeclaration : import ModuleSpecifier WithClauseopt ;
  1. Return a new empty List.

28 Reflection

28.3 Module Namespace Objects

A Module Namespace Object is a module namespace exotic object that provides runtime property-based access to a module's exported bindings. There is no constructor function for Module Namespace Objects. Instead, such an object is created for each module that is imported by an ImportDeclaration that contains a NameSpaceImport.

In addition to the properties specified in 10.4.6 each Module Namespace Object has the following own property:

28.3.1 %Symbol.toStringTag%

The initial value of the %Symbol.toStringTag% property is the String value "Module".

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.

A Copyright & Software License

Copyright Notice

© 2025 Nicolò Ribaudo

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.