Stage 2 Draft / July 4, 2022

JavaScript Module Blocks

1 Primary Expression

Syntax

PrimaryExpression[Yield, Await] : IdentifierReference[?Yield, ?Await] Literal ArrayLiteral[?Yield, ?Await] ObjectLiteral[?Yield, ?Await] FunctionExpression ClassExpression[?Yield, ?Await] GeneratorExpression AsyncFunctionExpression AsyncGeneratorExpression ModuleBlockExpression RegularExpressionLiteral TemplateLiteral[?Yield, ?Await, ~Tagged] CoverParenthesizedExpressionAndArrowParameterList[?Yield, ?Await]

2 Module Block Definitions

Syntax

ModuleBlockExpression : module [no LineTerminator here] { ModuleBodyopt }

2.1 Runtime Semantics: Evaluation

PrimaryExpression : ModuleBlockExpression
  1. Let moduleBlock be OrdinaryObjectCreate(%ModuleBlock.prototype%, « [[HostDefined]], [[ModuleBlockBody]], [[SourceText]] »).
  2. Set moduleBlock.[[SourceText]] to the source text matched by ModuleBlockExpression.
  3. Let parsedBody be ? ParseText(body, Module).
  4. Set moduleBlock.[[ModuleBlockBody]] to parsedBody.
  5. Perform ! HostInitializeModuleBlock(moduleBlock).
  6. Return moduleBlock.

3 ModuleBlock ( body )

  1. Throw a TypeError exception.

4 ModuleBlock.prototype

The initial value of ModuleBlock.prototype is the ModuleBlock prototype object.

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

5 Properties of the ModuleBlock Prototype Object

The ModuleBlock prototype object:

5.1 ModuleBlock.prototype.toString ( )

The following steps are taken:

  1. Let module be the this value.
  2. If Type(module) is Object and has the internal slot [[ModuleBlockBody]]
    1. Return module.[[SourceText]].
  3. Throw a TypeError exception.

6 Import Calls

6.1 Runtime Semantics: Evaluation

ImportCall : import ( AssignmentExpression )
  1. Let referencingScriptOrModule be GetActiveScriptOrModule().
  2. Let argRef be the result of evaluating AssignmentExpression.
  3. Let specifier be ? GetValue(argRef).
  4. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  5. Let specifierString be ToString(specifier).
  6. If Type(specifier) is not Object or specifier does not have the internal slot [[ModuleBlockBody]]
    1. Set specifier to ToString(specifier).
    2. IfAbruptRejectPromise(specifierStringspecifier, promiseCapability).
  7. Perform ! HostImportModuleDynamicallyImportModuleDynamically(referencingScriptOrModule, specifierStringspecifier, promiseCapability).
  8. Return promiseCapability.[[Promise]].

7 HostInitializeModuleBlock ( moduleBlock )

The host-defined abstract operation HostInitializeModuleBlock takes argument moduleBlock (a Module Block). Initializes a Module Block with host-dependent information.

8 Executable Code and Execution Contexts

8.1 Realms

Before it is evaluated, all ECMAScript code must be associated with a realm. Conceptually, a realm consists of a set of intrinsic objects, an ECMAScript global environment, all of the ECMAScript code that is loaded within the scope of that global environment, and other associated state and resources.

A realm is represented in this specification as a Realm Record with the fields specified in Table 1:

Table 1: Realm Record Fields
Field Name Value Meaning
[[Intrinsics]]
[[GlobalObject]]
[[GlobalEnv]]
[[TemplateMap]]
[[ModuleMap]] a List of Record { [[ReferencingScriptOrModule]]: Script Record or Module Record or null, [[Specifier]]: String or Object, [[ModuleRecord]]: Module Record }

Module Records are cached separately per Realm: two imports from the same realm using the same specifier from the same referencing script or module must resolve to the same Module Record. The cache needs to be per-Realm to support cases when [[ReferencingScriptOrModule]] is null.

This List respects the following invariants:

  • It does not contain two Records record1 and record2 such that record1.[[ReferencingScriptOrModule]] is record2.[[ReferencingScriptOrModule]] and record1.[[Specifier]] is record2.[[Specifier]].
  • For every Record record it contains, if Type(record.[[Specifier]]) is Object then:

    • record.[[Specifier]] has a [[ModuleBlockBody]] internal slot.
    • record.[[ReferencingScriptOrModule]] is null.
[[HostDefined]]

9 ECMAScript Language: Scripts and Modules

9.1 Modules

9.1.1 Module Semantics

9.1.1.1 Source Text Module Records

9.1.1.1.1 ParseModule ( sourceText, realm, hostDefined )

The abstract operation ParseModule takes arguments sourceText (ECMAScript source text or a Module Parse Node), realm, and hostDefined and returns a Source Text Module Record or a non-empty List of SyntaxError objects. It creates a Source Text Module Record based upon the result of parsing sourceText as a Module. If sourceText is a Parse Node, it always returns a Source Text Module Record. It performs the following steps when called:

  1. If sourceText is a Parse Node,
    1. Let body be sourceText.
  2. Else,
    1. Let body be ParseText(sourceText, Module).
    2. If body is a List of errors, return body.
  3. Let requestedModules be the ModuleRequests of body.
  4. Let importEntries be ImportEntries of body.
  5. Let importedBoundNames be ImportedLocalNames(importEntries).
  6. Let indirectExportEntries be a new empty List.
  7. Let localExportEntries be a new empty List.
  8. Let starExportEntries be a new empty List.
  9. Let exportEntries be ExportEntries of body.
  10. For each ExportEntry Record ee of exportEntries, do
    1. If ee.[[ModuleRequest]] is null, then
      1. If ee.[[LocalName]] is not an element of importedBoundNames, then
        1. Append ee to localExportEntries.
      2. Else,
        1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]].
        2. If ie.[[ImportName]] is namespace-object, then
          1. NOTE: This is a re-export of an imported module namespace object.
          2. Append ee to localExportEntries.
        3. Else,
          1. NOTE: This is a re-export of a single name.
          2. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries.
    2. Else if ee.[[ImportName]] is all-but-default, then
      1. Assert: ee.[[ExportName]] is null.
      2. Append ee to starExportEntries.
    3. Else,
      1. Append ee to indirectExportEntries.
  11. Let async be body Contains await.
  12. 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 }.

9.1.1.2 ResolveImportedModule ( referencingScriptOrModule, specifier )

The abstract operation ResolveImportedModule takes arguments referencingScriptOrModule (a Script Record, a Module Record, or null) and specifier (a String or an Object with a [[ModuleBlockBody]] internal slot) and returns either a normal completion containing a Module Record or a throw completion. It performs the following steps when called:

  1. Assert: If Type(specifier) is Object, then referencingScriptOrModule is null.
  2. Let currentRealm be the current Realm Record.
  3. If there exists a Record record in currentRealm.[[ModuleMap]] such that record.[[ReferencingScriptOrModule]] is referencingScriptOrModule and record.[[Specifier]] is specifier, then
    1. Return NormalCompletion(record.[[ModuleRecord]]).
  4. Assert: Type(specifier) is String.
  5. Let completion be Completion(HostResolveImportedModule(referencingScriptOrModule, specifier)).
  6. If completion is a normal completion, then
    1. Add Record { [[ReferencingScriptOrModule]]: referencingScriptOrModule, [[Specifier]]: specifier, [[ModuleRecord]]: completion.[[Value]] } to currentRealm.[[ModuleMap]].
  7. Return completion.
Editor's Note
Replace all the references to HostResolveImportedModule in the ecma262 specification with ResolveImportedModule.

9.1.1.3 HostResolveImportedModule ( referencingScriptOrModule, specifier )

The host-defined abstract operation HostResolveImportedModule takes arguments referencingScriptOrModule (a Script Record, a Module Record, or null) and specifier (a String) and returns either a normal completion containing a Module Record or a throw completion.

An implementation of HostResolveImportedModule must conform to the following requirements:

9.1.1.4 ImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability )

The abstract operation ImportModuleDynamically takes arguments referencingScriptOrModule (a Script Record, a Module Record, or null), specifier (a String or an Object with a [[ModuleBlockBody]] internal slot), and promiseCapability (a PromiseCapability Record) and returns unused. 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. It performs the following steps when called:

  1. If Type(specifier) is Object, then
    1. Set referencingScriptOrModule to null.
  2. Let success be a new Abstract Closure that captures referencingScriptOrModule, specifier, and promiseCapability and performs the following steps when called:
    1. Let promise be ! PromiseResolve(%Promise%, undefined).
    2. Perform FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, promise).
  3. Let failure be a new Abstract Closure with parameters (error) that captures referencingScriptOrModule, specifier, and promiseCapability and performs the following steps when called:
    1. Let pc be ! NewPromiseCapability(%Promise%).
    2. Perform ! Call(pc.[[Reject]], undefined, « error »).
    3. Perform FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, pc.[[Promise]]).
  4. Let currentRealm be the current Realm Record.
  5. If there exists a Record record in currentRealm.[[ModuleMap]] such that record.[[ReferencingScriptOrModule]] is referencingScriptOrModule and record.[[Specifier]] is specifier, then
    1. Perform success().
  6. Else if Type(specifier) is Object, then
    1. Perform ImportModuleBlockDynamically(specifier, success, failure).
  7. Else,
    1. Perform HostImportModuleDynamically(referencingScriptOrModule, specifier, success, failure).

9.1.1.5 ImportModuleBlockDynamically ( moduleBlock, success, failure )

The abstract operation ImportModuleBlockDynamically takes arguments moduleBlock (an Object with a [[ModuleBlockBody]] internal slot), success (an Abstract Closure), and failure (an Abstract Closure) and returns unused. It links and evaluates the module represented by moduleBlock, and if it succeeds it adds the resulting Module Record to the current Realm Record's [[ModuleMap]] List. It performs the following steps when called:

  1. Let currentRealm be the current Realm Record.
  2. Let moduleRecord be ParseModule(moduleBlock.[[ModuleBlockBody]], currentRealm, moduleBlock.[[HostDefined]]).
  3. Assert: moduleRecord is a Source Text Module Record.
  4. Let linkResult be Completion(moduleRecord.Link()).
  5. If linkResult is an abrupt completion, then
    1. Perform failure(linkResult.[[Value]]).
    2. Return unused.
  6. Let evaluationPromise be moduleRecord.Evaluate().
  7. Let fulfilledClosure be a new Abstract Closure that captures success, currentRealm and moduleRecord and performs the following steps when called:
    1. If there exists a Record record in currentRealm.[[ModuleMap]] such that record.[[Specifier]] is specifier, then
      1. Assert: record.[[ModuleRecord]] is moduleRecord.
      2. TODO: Check if this is enough to protect against concurrent calls to ImportModuleBlockDynamically with the same moduleBlock. We do not want to insert the record twice.
    2. Else,
      1. Add Record { [[ReferencingScriptOrModule]]: null, [[Specifier]]: specifier, [[ModuleRecord]]: moduleRecord } to currentRealm.[[ModuleMap]].
    3. Perform success().
  8. Let onRejected be CreateBuiltinFunction(failure, 1, "", « »).
  9. Perform PerformPromiseThen(evaluationPromise, onFulfilled, onRejected).
  10. Return unused.

9.1.1.6 HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability, success, failure )

The host-defined abstract operation HostImportModuleDynamically takes arguments referencingScriptOrModule (a Script Record, a Module Record, or null), specifier (a String), promiseCapability (a PromiseCapability Record), success (an Abstract Closure), and failure (an Abstract Closure) and returns unused.

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
    Failure path
    • At some future time, the host environment must perform FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, promise)failure(error), where promiseerror is a Promise rejected with an error representing the cause of failure.
  • If the host environment takes the success path once for a given referencingScriptOrModule, specifier pair, it must always do so for subsequent calls.
  • 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 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.

A Copyright & Software License

Copyright Notice

© 2022 Surma, Dan Ehrenberg

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.