Stage 3 Draft / April 30, 2021

import assertions

See the explainer for information.

1 Syntax

ImportDeclaration:importImportClauseFromClause; importModuleSpecifier; importImportClauseFromClause[no LineTerminator here]AssertClause; importModuleSpecifier[no LineTerminator here]AssertClause; ExportDeclaration:exportExportFromClauseFromClause; exportExportFromClauseFromClause[no LineTerminator here]AssertClause; exportNamedExports; exportVariableStatement[~Yield, ~Await] exportDeclaration[~Yield, ~Await] exportdefaultHoistableDeclaration[~Yield, ~Await, +Default] exportdefaultClassDeclaration[~Yield, ~Await, +Default] exportdefault[lookahead ∉ { function, async [no LineTerminator here] function, class }]AssignmentExpression[+In, ~Yield, ~Await]; AssertClause:assert{} assert{AssertEntries,opt} AssertEntries:AssertionKey:StringLiteral AssertionKey:StringLiteral,AssertEntries AssertionKey:IdentifierName StringLiteral ImportCall[Yield, Await]:import(AssignmentExpression[+In, ?Yield, ?Await],opt) import(AssignmentExpression[+In, ?Yield, ?Await],AssignmentExpression[+In, ?Yield, ?Await],opt)

2 Semantics

Editor's Note

Many productions operating on grammar are the same whether or not an AssertClause/second ImportCall parameter is included; the new parameter is ignored. In this section, only the semantically significant changes are included, and the PR to merge into the main specification would fill in the straightforward details.

2.1 Import Calls

2.1.1 Runtime Semantics: Evaluation

2.1.1.1 EvaluateImportCall ( specifierExpression [ , optionsExpression ] )

  1. Let referencingScriptOrModule be ! GetActiveScriptOrModule().
  2. Let specifierRef be the result of evaluating specifierExpression.
  3. Let specifier be ? GetValue(specifierRef).
  4. If optionsExpression is present, then
    1. Let optionsRef be the result of evaluating optionsExpression.
    2. Let options be ? GetValue(optionsRef).
  5. Else,
    1. Let options be undefined.
  6. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  7. Let specifierString be ToString(specifier).
  8. IfAbruptRejectPromise(specifierString, promiseCapability).
  9. Let assertions be a new empty List.
  10. If options is not undefined, then
    1. If Type(options) is not Object,
      1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
      2. Return promiseCapability.[[Promise]].
    2. Let assertionsObj be Get(options, "assert").
    3. IfAbruptRejectPromise(assertionsObj, promiseCapability).
    4. If assertionsObj is not undefined,
      1. If Type(assertionsObj) is not Object,
        1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
        2. Return promiseCapability.[[Promise]].
      2. Let keys be EnumerableOwnPropertyNames(assertionsObj, key).
      3. IfAbruptRejectPromise(keys, promiseCapability).
      4. Let supportedAssertions be ! HostGetSupportedImportAssertions().
      5. For each String key of keys,
        1. Let value be Get(assertionsObj, key).
        2. IfAbruptRejectPromise(value, promiseCapability).
        3. If Type(value) is not String, then
          1. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
          2. Return promiseCapability.[[Promise]].
        4. If supportedAssertions contains key, then
          1. Append { [[Key]]: key, [[Value]]: value } to assertions.
    5. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in.
  11. Let moduleRequest be a new ModuleRequest Record { [[Specifier]]: specifierString, [[Assertions]]: assertions }.
  12. Perform ! HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability).
  13. Return promiseCapability.[[Promise]].
ImportCall:import(AssignmentExpression,opt)
  1. Return ? EvaluateImportCall(AssignmentExpression).
ImportCall:import(AssignmentExpression,AssignmentExpression,opt)
  1. Return ? EvaluateImportCall(the first AssignmentExpression, the second AssignmentExpression).

2.2 Runtime Semantics: HostResolveImportedModule ( referencingScriptOrModule, specifier moduleRequest )

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

Note

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

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

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

The implementation of HostResolveImportedModule must conform to the following requirements:

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

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

Editor's Note

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

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

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

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

2.3 Runtime Semantics: HostImportModuleDynamically ( referencingScriptOrModule, specifier, moduleRequest, promiseCapability )

HostImportModuleDynamically is an implementation-defined abstract operation that performs any necessary setup work in order to make available the module corresponding to the ModuleSpecifier String, specifier,ModuleRequest Record moduleRequest occurring within the context of the script or module represented by the Script Record or Module Record referencingScriptOrModule. (referencingScriptOrModule may also be null, if there is no active script or module when the import() expression occurs.) It then performs FinishDynamicImport to finish the dynamic import process.

The implementation of HostImportModuleDynamically must conform to the following requirements:

  • The abstract operation must always complete normally with undefined. 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, moduleRequest, promiseCapability, NormalCompletion(undefined)).
    • Any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed, given the arguments referencingScriptOrModule, and specifier moduleRequest must complete normally.
    • The completion value of any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed, given the arguments referencingScriptOrModule, and specifier, moduleRequest must be a module which has already been evaluated, i.e. whose Evaluate concrete method has already been called and returned a normal completion.
    Failure path
  • If the host environment takes the success path once for a given referencingScriptOrModule, specifier, moduleRequest 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 implementation-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.

2.4 Runtime Semantics: FinishDynamicImport ( referencingScriptOrModule, specifier, moduleRequest, promiseCapability, completion )

The abstract operation FinishDynamicImport takes arguments referencingScriptOrModule, specifier, moduleRequest (a ModuleRequest Record), promiseCapability, and completion. 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 completion. It is performed by host environments as part of HostImportModuleDynamically. It performs the following steps when called:

  1. If completion is an abrupt completion, then perform ! Call(promiseCapability.[[Reject]], undefined, « completion.[[Value]] »).
  2. Else,
    1. Assert: completion is a normal completion and completion.[[Value]] is undefined.
    2. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier, moduleRequest).
    3. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
    4. Let namespace be GetModuleNamespace(moduleRecord).
    5. If namespace is an abrupt completion, perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
    6. Else, perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).

2.5 Static Semantics: HostGetSupportedImportAssertions ()

HostGetSupportedImportAssertions is a host-defined abstract operation that allows host environments to specify which import assertions they support. Only assertions with supported keys will be provided to the host.

The implementation of HostGetSupportedImportAssertions must conform to the following requrements:

  • It must return a List whose values are all StringValues, each indicating a supported assertion.
  • Each time this operation is called, it must return the same List instance with the same contents.
  • An implementation of HostGetSupportedImportAssertions must always complete normally (i.e., not return an abrupt completion).

The default implementation of HostGetSupportedImportAssertions is to return an empty List.

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

2.6 Static Semantics: Early Errors

AssertClause:assert{AssertEntries,opt}

2.7 Static Semantics: AssertClauseToAssertions

AssertClause:assert{}
  1. Return a new empty List.
AssertClause:assert{AssertEntries,opt}
  1. Let assertions be AssertClauseToAssertions of AssertEntries.
  2. Sort assertions by the code point order of the [[Key]] of each element. NOTE: This sorting is observable only in that hosts are prohibited from distinguishing among assertions by the order they occur in.
  3. Return assertions.
AssertEntries:AssertionKey:StringLiteral
  1. Let supportedAssertions be !HostGetSupportedImportAssertions().
  2. Let key be StringValue of AssertionKey.
  3. If supportedAssertions contains key,
    1. Let entry be a Record { [[Key]]: key, [[Value]]: StringValue of StringLiteral }.
    2. Return a new List containing the single element, entry.
  4. Otherwise, return a new empty List.
AssertEntries:AssertionKey:StringLiteral,AssertEntries
  1. Let supportedAssertions be !HostGetSupportedImportAssertions().
  2. Let key be StringValue of AssertionKey.
  3. If supportedAssertions contains key,
    1. Let entry be a Record { [[Key]]: key, [[Value]]: StringValue of StringLiteral }.
    2. Let rest be AssertClauseToAssertions of AssertEntries.
    3. Return a new List containing entry followed by the elements of rest.
  4. Otherwise, return AssertClauseToAssertions of AssertEntries.

2.8 Static Semantics: StringValue

AssertionKey:IdentifierName
  1. Return the StringValue of IdentifierName.
AssertionKey:StringLiteral
  1. Return the StringValue of StringLiteral.

2.9 ModuleRequest Records

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

Table 1: ModuleRequest Record fields
Field Name Value Type Meaning
[[Specifier]] String The module specifier
[[Assertions]] a List of Records { [[Key]]: a String, [[Value]]: a String } The import assertions
Editor's Note
In general, this proposal replaces places where module specifiers are passed around with ModuleRequest Records. For example, several syntax-directed operations, such as ModuleRequests produce Lists of ModuleRequest Records rather than Lists of Strings which are interpreted as module specifiers. Some algorithms like ImportEntries and ImportEntriesForModule pass around ModuleRequest Records rather than Strings, in a way which doesn't require any particular textual change. Additionally, record fields in Cyclic Module Records and Source Text Module Records which contained Lists of Strings are replaced by Lists of ModuleRequest Records, as indicated above.
Table 2: Additional Fields of Cyclic Module Records
Field Name Value Type Meaning
[[Status]] unlinked | linking | linked | evaluating | evaluated Initially unlinked. Transitions to linking, linked, evaluating, evaluated (in that order) as the module progresses throughout its lifecycle.
[[EvaluationError]] An abrupt completion | undefined A completion of type throw representing the exception that occurred during evaluation. undefined if no exception occurred or if [[Status]] is not evaluated.
[[DFSIndex]] Integer | undefined Auxiliary field used during Link and Evaluate only. If [[Status]] is linking or evaluating, this nonnegative number records the point at which the module was first visited during the ongoing depth-first traversal of the dependency graph.
[[DFSAncestorIndex]] Integer | undefined Auxiliary field used during Link and Evaluate only. If [[Status]] is linking or evaluating, this is either the module's own [[DFSIndex]] or that of an "earlier" module in the same strongly connected component.
[[RequestedModules]] List of StringModuleRequest Record A List of all the ModuleSpecifier strings and import assertions used by the module represented by this record to request the importation of a module. The List is source code occurrence ordered.

An ImportEntry Record is a Record that digests information about a single declarative import. Each ImportEntry Record has the fields defined in Table 3:

Table 3: ImportEntry Record Fields
Field Name Value Type Meaning
[[ModuleRequest]] String ModuleRequest Record String value of the ModuleSpecifier of the ImportDeclaration. ModuleRequest Record representing the ModuleSpecifier and import assertions of the ImportDeclaration.
[[ImportName]] String The name under which the desired binding is exported by the module identified by [[ModuleRequest]]. The value "*" indicates that the import request is for the target module's namespace object.
[[LocalName]] String The name that is used to locally access the imported value from within the importing module.

2.10 Static Semantics: ModuleRequests

ImportDeclaration:importImportClauseFromClause;
  1. Return ModuleRequests of FromClause.
  2. Let specifier be StringValue of the StringLiteral contained in FromClause.
  3. Return a ModuleRequest Record { [[Specifer]]: specifier, [[Assertions]]: an empty List }.
ImportDeclaration:importImportClauseFromClauseAssertClause;
  1. Let specifier be StringValue of the StringLiteral contained in FromClause.
  2. Let assertions be AssertClauseToAssertions of AssertClause.
  3. Return a ModuleRequest Record { [[Specifer]]: specifier, [[Assertions]]: assertions }.
Module:[empty]
  1. Return a new empty List.
ModuleItemList:ModuleItem
  1. Return ModuleRequests of ModuleItem.
ModuleItemList:ModuleItemListModuleItem
  1. Let moduleNames be ModuleRequests of ModuleItemList.
  2. Let additionalNames be ModuleRequests of ModuleItem.
  3. Append to moduleNames each element of additionalNames that is not already an element of moduleNames.
  4. Return moduleNames.
Editor's Note
Deletion of duplicates is an unnecessary "spec optimization" that would be more complicated to explain in terms of examining import assertion records, and can be simply removed.
ModuleItem:StatementListItem
  1. Return a new empty List.
ExportDeclaration:exportExportFromClauseFromClause;
  1. Return ModuleRequests of FromClause.
  2. Let specifier be StringValue of the StringLiteral contained in FromClause.
  3. Return a ModuleRequest Record { [[Specifer]]: specifier, [[Assertions]]: an empty List }.
ExportDeclaration:exportExportFromClauseFromClauseAssertClause;
  1. Let specifier be StringValue of the StringLiteral contained in FromClause.
  2. Let assertions be AssertClauseToAssertions of AssertClause.
  3. Return a ModuleRequest Record { [[Specifer]]: specifier, [[Assertions]]: assertions }.
ExportDeclaration:exportNamedExports; exportVariableStatement exportDeclaration exportdefaultHoistableDeclaration exportdefaultClassDeclaration exportdefaultAssignmentExpression;
  1. Return a new empty List.

A Sample host integration: The Web embedding

The import assertions proposal is intended to give key information about how modules are interpreted to hosts. For the Web embedding and environments which aim to be similar to it, the string is interpreted as the "module type". This is not the primary way the module type is determined (which, on the Web, would be the MIME type, and in other environments may be the file extension), but rather a secondary check which is required to pass for the module graph to load.

In the Web embedding, the following changes would be made to the HTML specification for import assertions:

The module map is keyed by the absolute URL and the type. Initially no other import assertions are supported, so they are not present.