From 36635e064d85aa9d403776d8532eb516695f65b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 15 Sep 2022 23:21:25 +0900 Subject: [PATCH] Layering: Add HostLoadImportedModule hook This commit also removed the HostResolveImportedModule and HostImportModuleDynamically hooks --- spec.html | 441 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 334 insertions(+), 107 deletions(-) diff --git a/spec.html b/spec.html index b9891301320..5e58d3c225b 100644 --- a/spec.html +++ b/spec.html @@ -11676,6 +11676,20 @@

Realms

Once a Parse Node becomes unreachable, the corresponding [[Array]] is also unreachable, and it would be unobservable if an implementation removed the pair from the [[TemplateMap]] list. + + + [[LoadedModules]] + + + a List of Records { [[Specifier]]: a String, [[Module]]: a Module Record } + + + A map from the specifier strings imported by this realm to the resolved Module Record. The list does not contain two different Records with the same [[Specifier]]. + + As mentioned in HostLoadImportedModule, [[LoadedModules]] in Realm Records is only used when running an `import()` expression in a context where there is no active script or module. + + + [[HostDefined]] @@ -19404,15 +19418,61 @@

Runtime Semantics: Evaluation

ImportCall : `import` `(` AssignmentExpression `)` - 1. Let _referencingScriptOrModule_ be GetActiveScriptOrModule(). + 1. Let _referrer_ be GetActiveScriptOrModule(). + 1. If _referrer_ is *null*, set _referrer_ to the current Realm Record. 1. Let _argRef_ be ? Evaluation of |AssignmentExpression|. 1. Let _specifier_ be ? GetValue(_argRef_). 1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). 1. Let _specifierString_ be Completion(ToString(_specifier_)). 1. IfAbruptRejectPromise(_specifierString_, _promiseCapability_). - 1. Perform HostImportModuleDynamically(_referencingScriptOrModule_, _specifierString_, _promiseCapability_). + 1. Let _state_ be a new ModuleLoadState Record { [[Action]]: ~dynamic-import~, [[PromiseCapability]]: _promiseCapability_ }. + 1. Perform HostLoadImportedModule(_referrer_, _specifier_, *undefined*, _state_). 1. Return _promiseCapability_.[[Promise]]. + + +

+ ContinueDynamicImport ( + _state_: a ModuleLoadState Record whose [[Action]] is ~dynamic-import~, + _module_: either a normal completion containing a Module Record or a throw completion, + ): ~unused~ +

+
+
description
+
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.
+
+ + 1. Let _promiseCapability_ be _state_.[[PromiseCapability]]. + 1. If _module_ is an abrupt completion, then + 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _module_.[[Value]] »). + 1. Return ~unused~. + 1. Else, + 1. Set _module_ to _module_.[[Value]]. + 1. 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_ »). + 1. Return ~unused~. + 1. Let _onRejected_ be CreateBuiltinFunction(_rejectedClosure_, 1, *""*, « »). + 1. Let _linkAndEvaluateClosure_ be a new Abstract Closure with no parameters that captures _module_, _promiseCapability_, and _onRejected_ and performs the following steps when called: + 1. Let _link_ be Completion(_module_.Link()). + 1. If _link_ is an abrupt completion, then + 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _link_.[[Value]] »). + 1. Return ~unused~. + 1. Let _evaluatePromise_ be _module_.Evaluate(). + 1. Let _fulfilledClosure_ be a new Abstract Closure with no parameters that captures _module_ and _promiseCapability_ and performs the following steps when called: + 1. Let _namespace_ be Completion(GetModuleNamespace(_module_)). + 1. If _namespace_ is an abrupt completion, then + 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _namespace_.[[Value]] »). + 1. Else, + 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _namespace_.[[Value]] »). + 1. Return ~unused~. + 1. Let _onFulfilled_ be CreateBuiltinFunction(_fulfilledClosure_, *""*, 0, « »). + 1. Perform PerformPromiseThen(_evaluatePromise_, _onFulfilled_, _onRejected_). + 1. Let _linkAndEvaluate_ be CreateBuiltinFunction(_linkAndEvaluateClosure_, *""*, 0, « »). + 1. Let _loadPromise_ be _module_.LoadRequestedModules(). + 1. Perform PerformPromiseThen(_loadPromise_, _linkAndEvaluate_, _onRejected_). + 1. Return ~unused~. + +
@@ -25773,6 +25833,17 @@

Script Records

The result of parsing the source text of this script using |Script| as the goal symbol. + + + [[LoadedModules]] + + + a List of Records { [[Specifier]]: a String, [[Module]]: a Module Record } + + + A map from the specifier strings imported by this script to the resolved Module Record. The list does not contain two different Records with the same [[Specifier]]. + + [[HostDefined]] @@ -25804,7 +25875,7 @@

1. Let _script_ be ParseText(_sourceText_, |Script|). 1. If _script_ is a List of errors, return _script_. - 1. Return Script Record { [[Realm]]: _realm_, [[ECMAScriptCode]]: _script_, [[HostDefined]]: _hostDefined_ }. + 1. Return Script Record { [[Realm]]: _realm_, [[ECMAScriptCode]]: _script_, [[LoadedModules]]: a new empty List, [[HostDefined]]: _hostDefined_ }.

An implementation may parse script source text and analyse it for Early Error conditions prior to evaluation of ParseScript for that script source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseScript upon that source text.

@@ -26155,12 +26226,21 @@

Abstract Module Records

Each time this operation is called with a specific _exportName_, _resolveSet_ pair as arguments it must return the same result if it completes normally.

+ + + LoadRequestedModules( [ _hostDefined_ ] ) + + +

Prepares the module for linking by recursively loading all its dependencies, and returns a promise.

+ + Link()

Prepare the module for evaluation by transitively resolving all module dependencies and creating a Module Environment Record.

+

LoadRequestedModules must have completed successfully prior to invoking this method.

@@ -26198,10 +26278,10 @@

Cyclic Module Records

[[Status]] - ~unlinked~, ~linking~, ~linked~, ~evaluating~, ~evaluating-async~, or ~evaluated~ + ~new~, ~unlinked~, ~linking~, ~linked~, ~evaluating~, ~evaluating-async~, or ~evaluated~ - Initially ~unlinked~. Transitions to ~linking~, ~linked~, ~evaluating~, possibly ~evaluating-async~, ~evaluated~ (in that order) as the module progresses throughout its lifecycle. ~evaluating-async~ indicates this module is queued to execute on completion of its asynchronous dependencies or it is a module whose [[HasTLA]] field is *true* that has been executed and is pending top-level completion. + Initially ~new~. Transitions to ~unlinked~, ~linking~, ~linked~, ~evaluating~, possibly ~evaluating-async~, ~evaluated~ (in that order) as the module progresses throughout its lifecycle. ~evaluating-async~ indicates this module is queued to execute on completion of its asynchronous dependencies or it is a module whose [[HasTLA]] field is *true* that has been executed and is pending top-level completion. @@ -26248,6 +26328,17 @@

Cyclic Module Records

A List of all the |ModuleSpecifier| strings used by the module represented by this record to request the importation of a module. The List is source text occurrence ordered. + + + [[LoadedModules]] + + + a List of Record { [[Specifier]]: a String, [[Module]]: a Module Record } + + + A map from the specifier strings used by the module represented by this record to request the importation of a module to the resolved Module Record. The list does not contain two different Records with the same [[Specifier]]. + + [[CycleRoot]] @@ -26346,6 +26437,169 @@

Cyclic Module Records

+

A ModuleLoadState Record is a Record that contains information about the loading process of a module graph. It's used to continue loading after a call to HostLoadImportedModule. Each ModuleLoadState Record has the fields defined in :

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Field Name + + Value Type + + Meaning +
+ [[Action]] + + ~graph-loading~ or ~dynamic-import~ + + The action that caused the call to HostLoadImportedModule. It is ~graph-loading~ when loading the dependencies of a module; it is ~dynamic-import~ when loading a module that was requested by an `import()` call. +
+ [[PromiseCapability]] + + a PromiseCapability Record + + The promise to resolve when the loading process finishes. +
+ [[IsLoading]] + + a boolean + + This field is only used if [[Action]] is ~graph-loading~. It is true if the loading process has not finished yet, neither successfully nor with an error. +
+ [[PendingModules]] + + a non-negative integer + + This field is only used if [[Action]] is ~graph-loading~. It tracks the number of pending HostLoadImportedModule calls. +
+ [[Visited]] + + a List of Cyclic Module Records + + This field is only used if [[Action]] is ~graph-loading~. It is a list of the Cyclic Module Records that have been already loaded by the current loading process, to avoid infinite loops with circular dependencies. +
+ [[HostDefined]] + + anything (default value is *undefined*) + + It contains host-defined data to pass from the LoadRequestedModules caller to HostLoadImportedModule. +
+
+ + +

LoadRequestedModules ( [ _hostDefined_ ] ): a Promise object

+
+
for
+
a Cyclic Module Record _module_
+ +
description
+
It populates the [[LoadedModules]] of all the Modue Records in the dependency graph of _module_ (most of the work is done by the auxiliary function InnerModuleLoading). It takes an optional _hostDefined_ parameter, that is passed to the HostLoadImportedModule hook.
+
+ + + 1. If _hostDefined_ is not present, let _hostDefined_ be *undefined*. + 1. Let _pc_ be ! NewPromiseCapability(%Promise%). + 1. Let _state_ be a new ModuleLoadState Record { [[Action]]: ~graph-loading~, [[IsLoading]]: *true*, [[PendingModules]]: 1, [[Visited]]: a new empty List, [[PromiseCapability]]: _pc_, [[HostDefined]]: _hostDefined_ }. + 1. Perform InnerModuleLoading(_state_, _module_). + 1. Return _pc_.[[Promise]]. + + + + The _hostDefined_ parameter can be used to pass additional information necessary to fetch the imported modules. It is used, for example, by HTML to set the correct fetch destination for <link rel="preload" as="..."> tags. + import() expressions never set the _hostDefined_ parameter. + + + +

+ InnerModuleLoading ( + _state_: a ModuleLoadState Record whose [[Action]] is ~graph-loading~, + _module_: a Module Record, + ): ~unused~ +

+
+
description
+
It is used by LoadRequestedModules to recursively perform the actual loading process for _module_'s dependency graph.
+
+ + + 1. Assert: _state_.[[IsLoading]] is *true*. + 1. If _module_ is a Cyclic Module Record, _module_.[[Status]] is ~new~, and _state_.[[Visited]] does not contain _module_, then + 1. Add _module_ to _state_.[[Visited]]. + 1. Let _requestedModulesCount_ be the length of _module_.[[RequestedModules]]. + 1. Set _state_.[[PendingModules]] to _state_.[[PendingModules]] + _requestedModulesCount_. + 1. For each String _required_ of _module_.[[RequestedModules]], do + 1. If _module_.[[LoadedModules]] contains a Record _record_ such that _record_.[[Specifier]] is _required_, then + 1. Perform ContinueModuleLoading(_state_, NormalCompletion(_record_.[[Module]])). + 1. Else, + 1. Perform HostLoadImportedModule(_referrer_, _specifier_, _state_.[[HostDefined]], _state_). + 1. NOTE: HostLoadImportedModule will call ContinueModuleLoading. + 1. Assert: _state_.[[PendingModules]] ≥ 1. + 1. Set _state_.[[PendingModules]] to _state_.[[PendingModules]] - 1. + 1. If _state_.[[PendingModules]] = 0, then + 1. Set _state_.[[IsLoading]] to *false*. + 1. For each Cyclic Module Record _loaded_ in _state_.[[Visited]], do + 1. If _loaded_.[[Status]] is ~new~, set _loaded_.[[Status]] to ~unlinked~. + 1. Perform ! Call(_state_.[[PromiseCapability]].[[Resolve]], *undefined*, « *undefined* »). + +
+ + +

+ ContinueModuleLoading ( + _state_: a ModuleLoadState Record whose [[Action]] is ~graph-loading~, + _result_: either a normal completion containing a Module Record or a throw completion, + ): ~unused~ +

+
+
description
+
It is used to re-enter the loading process after a call to HostLoadImportedModule.
+
+ + + 1. If _state_.[[IsLoading]] is *false*, return. + 1. If _result_ is a normal completion, then + 1. Perform InnerModuleLoading(_state_, _result_.[[Value]]). + 1. Else, + 1. Set _state_.[[IsLoading]] to *false*. + 1. Perform ! Call(_state_.[[PromiseCapability]].[[Reject]], *undefined*, « _result_.[[Value]] »). + +
+
+

Link ( ): either a normal completion containing ~unused~ or a throw completion

@@ -26357,7 +26611,7 @@

Link ( ): either a normal completion containing ~unused~ or a throw completi

- 1. Assert: _module_.[[Status]] is not ~linking~ or ~evaluating~. + 1. Assert: _module_.[[Status]] is ~unlinked~, ~linked~, ~evaluating-async~, or ~evaluated~. 1. Let _stack_ be a new empty List. 1. Let _result_ be Completion(InnerModuleLinking(_module_, _stack_, 0)). 1. If _result_ is an abrupt completion, then @@ -26397,7 +26651,8 @@

1. Set _index_ to _index_ + 1. 1. Append _module_ to _stack_. 1. For each String _required_ of _module_.[[RequestedModules]], do - 1. Let _requiredModule_ be ? HostResolveImportedModule(_module_, _required_). + 1. Let _requiredModule_ be GetImportedModule(_module_, _required_). + 1. Assert: _requiredModule_ is not ~empty~, because LoadRequestedModules must have completed successfully prior to invoking this method. 1. Set _index_ to ? InnerModuleLinking(_requiredModule_, _stack_, _index_). 1. If _requiredModule_ is a Cyclic Module Record, then 1. Assert: _requiredModule_.[[Status]] is either ~linking~, ~linked~, ~evaluating-async~, or ~evaluated~. @@ -26490,8 +26745,8 @@

1. Set _index_ to _index_ + 1. 1. Append _module_ to _stack_. 1. For each String _required_ of _module_.[[RequestedModules]], do - 1. Let _requiredModule_ be ! HostResolveImportedModule(_module_, _required_). - 1. NOTE: Link must be completed successfully prior to invoking this method, so every requested module is guaranteed to resolve successfully. + 1. Let _requiredModule_ be GetImportedModule(_module_, _required_). + 1. Assert: _requiredModule_ is not ~empty~, because LoadRequestedModules must have completed successfully prior to invoking this method. 1. Set _index_ to ? InnerModuleEvaluation(_requiredModule_, _stack_, _index_). 1. If _requiredModule_ is a Cyclic Module Record, then 1. Assert: _requiredModule_.[[Status]] is either ~evaluating~, ~evaluating-async~, or ~evaluated~. @@ -26668,12 +26923,11 @@

Example Cyclic Module Record Graphs

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_.Link(), this will complete successfully by assumption, and recursively link modules _B_ and _C_ as well, such that _A_.[[Status]] = _B_.[[Status]] = _C_.[[Status]] = ~linked~. This preparatory step 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. 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.

+

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~.

-

Finally, consider a case involving evaluation errors. 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.)

+

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.

-

The difference here between linking and evaluation errors is due to how 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.

+

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:

@@ -26681,7 +26935,14 @@

Example Cyclic Module Record Graphs

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. HostResolveImportedModule throws 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 throwing an exception when trying to parse the resulting source text. Hosts can choose to expose the cause of failure via the exception they throw from HostResolveImportedModule. In any case, this exception causes a linking failure, which as before results in _A_'s [[Status]] remaining ~unlinked~.

+

In this scenario, module _A_ declares a dependency on some other module, but no Module Record exists for that module, i.e. HostLoadImportedModule calls FinishLoadImportedModule 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 FinishLoadImportedModule. 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:

@@ -26689,11 +26950,13 @@

Example Cyclic Module Record Graphs

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_.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.

+

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 an 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~.

+

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]].

@@ -26701,7 +26964,7 @@

Example Cyclic Module Record Graphs

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 -

Linking happens as before, and all modules end up with [[Status]] set to ~linked~.

+

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 .

@@ -27585,7 +27848,7 @@

1. Else, 1. Append _ee_ to _indirectExportEntries_. 1. Let _async_ be _body_ Contains `await`. - 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: ~empty~, [[Namespace]]: ~empty~, [[CycleRoot]]: ~empty~, [[HasTLA]]: _async_, [[AsyncEvaluation]]: *false*, [[TopLevelCapability]]: ~empty~, [[AsyncParentModules]]: « », [[PendingAsyncDependencies]]: ~empty~, [[Status]]: ~unlinked~, [[EvaluationError]]: ~empty~, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[Context]]: ~empty~, [[ImportMeta]]: ~empty~, [[RequestedModules]]: _requestedModules_, [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: ~empty~, [[DFSAncestorIndex]]: ~empty~ }. + 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: ~empty~, [[Namespace]]: ~empty~, [[CycleRoot]]: ~empty~, [[HasTLA]]: _async_, [[AsyncEvaluation]]: *false*, [[TopLevelCapability]]: ~empty~, [[AsyncParentModules]]: « », [[PendingAsyncDependencies]]: ~empty~, [[Status]]: ~new~, [[EvaluationError]]: ~empty~, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[Context]]: ~empty~, [[ImportMeta]]: ~empty~, [[RequestedModules]]: _requestedModules_, [[LoadedModules]]: a new empty List, [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: ~empty~, [[DFSAncestorIndex]]: ~empty~ }.

An implementation may parse module source text and analyse it for Early Error conditions prior to the evaluation of ParseModule for that module source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseModule upon that source text.

@@ -27616,7 +27879,8 @@

1. Assert: _module_ imports a specific binding for this export. 1. Append _e_.[[ExportName]] to _exportedNames_. 1. For each ExportEntry Record _e_ of _module_.[[StarExportEntries]], do - 1. Let _requestedModule_ be ? HostResolveImportedModule(_module_, _e_.[[ModuleRequest]]). + 1. Let _requiredModule_ be GetImportedModule(_module_, _e_.[[ModuleRequest]]). + 1. If _requiredModule_ is ~empty~, throw a *TypeError* exception. 1. Let _starNames_ be ? _requestedModule_.GetExportedNames(_exportStarSet_). 1. For each element _n_ of _starNames_, do 1. If SameValue(_n_, *"default"*) is *false*, then @@ -27660,7 +27924,8 @@

1. Return ResolvedBinding Record { [[Module]]: _module_, [[BindingName]]: _e_.[[LocalName]] }. 1. For each ExportEntry Record _e_ of _module_.[[IndirectExportEntries]], do 1. If SameValue(_exportName_, _e_.[[ExportName]]) is *true*, then - 1. Let _importedModule_ be ? HostResolveImportedModule(_module_, _e_.[[ModuleRequest]]). + 1. Let _importedModule_ be GetImportedModule(_module_, _e_.[[ModuleRequest]]). + 1. If _importedModule_ is ~empty~, throw a *TypeError* exception. 1. If _e_.[[ImportName]] is ~all~, then 1. Assert: _module_ does not provide the direct binding for this export. 1. Return ResolvedBinding Record { [[Module]]: _importedModule_, [[BindingName]]: ~namespace~ }. @@ -27673,7 +27938,8 @@

1. NOTE: A `default` export cannot be provided by an `export * from "mod"` declaration. 1. Let _starResolution_ be *null*. 1. For each ExportEntry Record _e_ of _module_.[[StarExportEntries]], do - 1. Let _importedModule_ be ? HostResolveImportedModule(_module_, _e_.[[ModuleRequest]]). + 1. Let _importedModule_ be GetImportedModule(_module_, _e_.[[ModuleRequest]]). + 1. If _importedModule_ is ~empty~, throw a *TypeError* exception. 1. Let _resolution_ be ? _importedModule_.ResolveExport(_exportName_, _resolveSet_). 1. If _resolution_ is ~ambiguous~, return ~ambiguous~. 1. If _resolution_ is not *null*, then @@ -27706,8 +27972,8 @@

InitializeEnvironment ( ): either a normal completion containing ~unused~ or 1. Let _env_ be NewModuleEnvironment(_realm_.[[GlobalEnv]]). 1. Set _module_.[[Environment]] to _env_. 1. For each ImportEntry Record _in_ of _module_.[[ImportEntries]], do - 1. Let _importedModule_ be ! HostResolveImportedModule(_module_, _in_.[[ModuleRequest]]). - 1. NOTE: The above call cannot fail because imported module requests are a subset of _module_.[[RequestedModules]], and these have been resolved earlier in this algorithm. + 1. Let _importedModule_ be GetImportedModule(_module_, _in_.[[ModuleRequest]]). + 1. Assert: _importedModule_ is not ~empty~, because imported module requests are a subset of _module_.[[RequestedModules]], and these have been resolved earlier in this algorithm. 1. If _in_.[[ImportName]] is ~namespace-object~, then 1. Let _namespace_ be ? GetModuleNamespace(_importedModule_). 1. Perform ! _env_.CreateImmutableBinding(_in_.[[LocalName]], *true*). @@ -27792,123 +28058,85 @@

- +

- HostResolveImportedModule ( - _referencingScriptOrModule_: a Script Record, a Module Record, or *null*, + GetImportedModule ( + _referrer_: a Cyclic Module Record, _specifier_: a String, - ): either a normal completion containing a Module Record or a throw completion + ): a Module Record or ~empty~

description
-
It provides the concrete Module Record subclass instance that corresponds to _specifier_ occurring within the context of the script or module represented by _referencingScriptOrModule_. _referencingScriptOrModule_ may 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.
+
- -

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.

-
- -

An implementation of HostResolveImportedModule must conform to the following requirements:

-
    -
  • - If the returned Completion Record is a normal completion, it must be a normal completion containing an instance of a concrete subclass of Module Record. -
  • -
  • - If a Module Record corresponding to the pair _referencingScriptOrModule_, _specifier_ does not exist or cannot be created, an exception must be thrown. -
  • -
  • - Each time this operation is called, if it completes normally and the (_referencingScriptOrModule_, _specifier_, current Realm Record) triple is the same, it must return the same Module Record instance. -
  • -
-

Multiple different _referencingScriptOrModule_, _specifier_ pairs may map to the same Module Record instance. The actual mapping semantic 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 alphabetic case folding and expansion of relative and abbreviated path specifiers.

+ + 1. If _referrer_.[[LoadedModules]] contains a Record _record_ such that _record_.[[Specifier]] is _specifier_, then + 1. Return _record_.[[Module]]. + 1. Return ~empty~. +
- +

- HostImportModuleDynamically ( - _referencingScriptOrModule_: a Script Record, a Module Record, or *null*, + HostLoadImportedModule ( + _referrer_: a Script Record, a Cyclic Module Record, or a Realm Record, _specifier_: a String, - _promiseCapability_: a PromiseCapability Record, + _hostDefined_: unknown, + _payload_: a ModuleLoadState Record, ): ~unused~

description
-
It performs any necessary setup work in order to make available the module corresponding to _specifier_ occurring within the context of the script or module represented by _referencingScriptOrModule_. _referencingScriptOrModule_ may be *null* if there is no active script or module when the `import()` expression occurs. It then performs FinishDynamicImport to finish the dynamic import process.
+
-

An implementation of HostImportModuleDynamically must conform to the following requirements:

- -
    -
  • - It must return ~unused~. Success or failure must instead be signaled as discussed below. -
  • -
  • - The host environment must conform to one of the two following sets of requirements: -
    -
    Success path
    -
    -
      -
    • At some future time, the host environment must perform FinishDynamicImport(_referencingScriptOrModule_, _specifier_, _promiseCapability_, _promise_), where _promise_ is a Promise resolved with *undefined*.
    • + +

      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

      -
    • Any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed, given the arguments _referencingScriptOrModule_ and _specifier_, must return a normal completion containing a module which has already been evaluated, i.e. whose Evaluate concrete method has already been called and returned a normal completion.
    • -
    -
    +
    <button type="button" onclick="import('./foo.mjs')">Click me</button>
    -
    Failure path
    +

    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.

    + -
    -
      -
    • At some future time, the host environment must perform FinishDynamicImport(_referencingScriptOrModule_, _specifier_, _promiseCapability_, _promise_), where _promise_ is a Promise rejected with an error representing the cause of failure.
    • -
    -
    -
    +

    An implementation of HostLoadImportedModule must conform to the following requirements:

    +
      +
    • + The host environment must perform FinishLoadImportedModule(_referrer_, _specifier_, _payload_, _result_), where _result_ is either a normal completion containing the loaded Module Record or a throw completion, either synchronously or asynchronously.
    • - If the host environment takes the success path once for a given _referencingScriptOrModule_, _specifier_ pair, it must always do so for subsequent calls. + If this operation is called multiple times with the same (_referrer_, _specifier_) pair and it performs FinishLoadImportedModule(_referrer_, _specifier_, _payload_, _result_) where _result_ is a normal completion, then it must perform FinishLoadImportedModule(_referrer_, _specifier_, _payload_, _result_) with the same _result_ each time.
    • - The operation must not call _promiseCapability_.[[Resolve]] or _promiseCapability_.[[Reject]], but instead must treat _promiseCapability_ as an opaque identifying value to be passed through to FinishDynamicImport. + The operation must treat _payload_ as an opaque value to be passed through to FinishLoadImportedModule.
    -

    The actual process performed is host-defined, but typically consists of performing whatever I/O operations are necessary to allow HostResolveImportedModule to synchronously retrieve the appropriate Module Record, and then calling its Evaluate concrete method. This might require performing similar normalization as HostResolveImportedModule does.

    +

    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_, _specifier_) pairs may map to the same Module Record instance. The actual mapping semantic 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.

    - +

    - FinishDynamicImport ( - _referencingScriptOrModule_: unknown, - _specifier_: unknown, - _promiseCapability_: a PromiseCapability Record, - _innerPromise_: unknown, + FinishLoadImportedModule ( + _referrer_: a Script Record, a Cyclic Module Record, or a Realm Record, + _specifier_: a String, + _payload_: a ModuleLoadState Record, + _result_: either a normal completion containing a Module Record or a throw completion, ): ~unused~

    description
    -
    FinishDynamicImport completes the process of a dynamic import originally started by an `import()` call, resolving or rejecting the promise returned by that call as appropriate according to _innerPromise_'s resolution. It is performed by host environments as part of HostImportModuleDynamically.
    +
    - 1. Let _fulfilledClosure_ be a new Abstract Closure with parameters (_result_) that captures _referencingScriptOrModule_, _specifier_, and _promiseCapability_ and performs the following steps when called: - 1. Assert: _result_ is *undefined*. - 1. Let _moduleRecord_ be ! HostResolveImportedModule(_referencingScriptOrModule_, _specifier_). - 1. Assert: Evaluate has already been invoked on _moduleRecord_ and successfully completed. - 1. Let _namespace_ be Completion(GetModuleNamespace(_moduleRecord_)). - 1. If _namespace_ is an abrupt completion, then - 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _namespace_.[[Value]] »). - 1. Else, - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _namespace_.[[Value]] »). - 1. Return ~unused~. - 1. Let _onFulfilled_ be CreateBuiltinFunction(_fulfilledClosure_, 0, *""*, « »). - 1. Let _rejectedClosure_ be a new Abstract Closure with parameters (_error_) that captures _promiseCapability_ and performs the following steps when called: - 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _error_ »). - 1. Return ~unused~. - 1. Let _onRejected_ be CreateBuiltinFunction(_rejectedClosure_, 0, *""*, « »). - 1. Perform PerformPromiseThen(_innerPromise_, _onFulfilled_, _onRejected_). - 1. Return ~unused~. + 1. If _result_ is a normal completion, then + 1. If _referrer_.[[LoadedModules]] contains a Record _record_ such that _record_.[[Specifier]] is _specifier_, then + 1. Assert: _record_.[[Module]] is _result_.[[Value]]. + 1. Else, add Record { [[Specifier]]: _specifier_, [[Module]]: _result_.[[Value]] } to _referrer_.[[LoadedModules]]. + 1. If _state_.[[Action]] is ~graph-loading~, then + 1. Perform ContinueModuleLoading(_state_, _result_). + 1. Else, + 1. Perform ContinueDynamicImport(_state_, _result_).
    @@ -27936,7 +28164,7 @@

    1. Return _namespace_. -

    The only way GetModuleNamespace can throw is via one of the triggered HostResolveImportedModule calls. 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.

    +

    If _module_ is a Cyclic Module Record, the only way GetModuleNamespace can throw is if one of its recursive dependencies has not been loaded. 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.

    @@ -48255,10 +48483,9 @@

    Host Hooks

    HostFinalizeImportMeta(...)

    HostGetImportMetaProperties(...)

    HostHasSourceTextAvailable(...)

    -

    HostImportModuleDynamically(...)

    +

    HostLoadImportedModule(...)

    HostMakeJobCallback(...)

    HostPromiseRejectionTracker(...)

    -

    HostResolveImportedModule(...)

    InitializeHostDefinedRealm(...)