Loader

A Collection of Interesting Ideas — Last Updated

This version:
https://whatwg.github.io/loader
Issue Tracking:
GitHub
Editors:
Eric Ferraiuolo (Yahoo)
Dave Herman (Mozilla)
Yehuda Katz (jQuery Foundation)
Caridy Patiño (Yahoo)
Version History:
https://github.com/whatwg/loader/commits
Participate:
File an issue (open issues)

Abstract

This specification describes the behavior of loading JavaScript modules from a JavaScript host environment. It also provides APIs for intercepting the module loading process and customizing loading behavior.

Table of Contents

Status

This document is a work in progress and dreams of becoming a living standard.

1. Module Loading

This section is non-normative.

1.1. Introduction

Throughout their development, JavaScript modules have been divided into two general areas:

The authoring format was carefully designed to support pre-compilation (like Browserify) and on-demand asynchronous loading (like AMD). It defines the minimal syntax necessary to allow people to write portable modules that can work across different platforms, most notably Node.js and web browsers.

The JavaScript Loader allows host environments, like Node.js and browsers, to fetch and load modules on demand. It provides a hookable pipeline, to allow front-end packaging solutions like Browserify, WebPack and jspm to hook into the loading process.

This division provides a single format that developers can use in all JavaScript environments, and a separate loading mechanism for each environment. For example, a Node Loader would load its modules from the file system, using its own module lookup algorithm, while a Browser Loader would fetch modules and use browser-supplied packaging formats.

JavaScript itself, in ECMAScript 2015, defines the module syntax and the "linking semantics" between modules. When a module is requested, it delegates responsibility for loading the module to the host environment. The Loader defines how host environments can allow JavaScript code to configure that process.

The primary goal is to make as much of this process as possible consistent between Node and Browser environments. For example, if a JavaScript program wants to translate .coffee files to JavaScript on the fly, the Loader defines a "translate" hook that can be used. This allows programs to participate in the loading process, even though some details (specifically, the process of getting a particular module from its host-defined storage) will be different between environments.

1.2. Loader Pipeline

TODO: include pipeline diagram

2. Conventions

2.1. Well-Known Symbols

Well-known symbols are built-in Symbol values that are explicitly referenced by algorithms of this specification. They are typically used as the keys of properties whose values serve as extension points of a specification algorithm.

Within this specification a well-known symbol is referred to by using a notation of the form @@name, where "name" is one of the values listed in table below:

Specification Name [[Description]] Value and Purpose
@@resolve "Reflect.Loader.resolve" A function valued property that is the resolve hook function of loader’s instances.
@@fetch "Reflect.Loader.fetch" A function valued property that is the fetch hook function of loader’s instances.
@@translate "Reflect.Loader.translate" A function valued property that is the translate hook function of loader’s instances.
@@instantiate "Reflect.Loader.instantiate" A function valued property that is the instantiate hook function of loader’s instances.

2.2. Well-Known Intrinsic Objects

Well-known intrinsics are built-in objects that are explicitly referenced by the algorithms of this specification and which usually have Realm specific identities. Unless otherwise specified each intrinsic object actually corresponds to a set of similar objects, one per Realm.

Within this specification a reference such as %name% means the intrinsic object, associated with the current Realm, corresponding to the name. Determination of the current Realm and its intrinsics is described in ES2015, 8.3.

2.3. Promises

This spec makes heavy use of promises, and adopts the notational conventions established in the promises guide.

2.3.1. Reacting to Promises

Transforming p with a new pass-through promise is a shorthand for wrapping the promise to avoid exposing the original promise. It represents the following step:

  1. Transforming p with a fulfillment handler that, when called with argument value, returns value.

2.4. Shorthand Phrases

2.4.1. RejectIfAbrupt(x)

Algorithm steps that say

  1. RejectIfAbrupt(x).

mean the same thing as:

  1. If x is an abrupt completion, then return a promise rejected with x.[[Value]].
  2. Else if x is a Completion Record, then let x be x.[[Value]].

2.5. Common Operations

2.5.1. CreateObject()

  1. Let obj be ObjectCreate(%ObjectPrototype%).
  2. Return obj.

2.5.2. SimpleDefine(obj, name, value)

  1. Let desc be a new PropertyDescriptor record {[[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
  2. Return ? OrdinaryDefineOwnProperty(obj, name, desc).

2.6. Built-in Function Objects

We follow the ECMA-262 convention for built-in function objects in which the value of NewTarget in each function argument is **undefined** for [[Call]] and the newTarget parameter for [[Construct]].

3. Loader Objects

3.1. The Loader Constructor

The Loader constructor is the %Loader% intrinsic object and the initial value of the Loader property of the the Reflect object. When called as a constructor it creates and initializes a new Loader object. When Loader is called as a function rather than as a constructor, it throws an exception.

The Loader constructor is designed to be subclassable. It may be used as the value of an extends clause of a class definition. Subclass constructors that intend to inherit the specified Loader behaviour must include a super call to the Loader constructor to create and initialize the subclass instance with the corresponding internal slots.

3.1.1. Loader()

When Loader is called with no arguments, the following steps are taken:

  1. If NewTarget is undefined, then throw a TypeError exception.
  2. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%LoaderPrototype%").
  3. Set O’s [[Registry]] internal slot to CreateRegistry().
  4. Return O.

3.2. Properties of the Loader Constructor

The value of the [[Prototype]] internal slot of the Loader constructor is the intrinsic object %FunctionPrototype%.

Besides the internal slots and the length property (whose value is 0), the Loader constructor has the following properties:

3.2.1. Loader.prototype

The value of Loader.prototype is %LoaderPrototype%.

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

3.3. Properties of the Loader Prototype Object

3.3.1. Loader.prototype.constructor

The initial value of Loader.prototype.constructor is the intrinsic object %Loader%.

3.3.2. Loader.prototype.import(name[, referrer])

The following steps are taken:

  1. Let loader be this value.
  2. If Type(loader) is not Object, throw a TypeError exception.
  3. If loader does not have all of the internal slots of a Loader Instance (3.5), throw a TypeError exception.
  4. Return the result of transforming Resolve(loader, name, referrer) with a fulfillment handler that, when called with argument key, runs the following steps:
    1. Let entry be EnsureRegistered(loader, key).
    2. Return LoadModule(entry, "ready").

3.3.3. Loader.prototype.resolve(name[, referrer])

The following steps are taken:

  1. Let loader be this value.
  2. If Type(loader) is not Object, throw a TypeError exception.
  3. If loader does not have all of the internal slots of a Loader Instance (3.5), throw a TypeError exception.
  4. Return Resolve(loader, name, referrer).

3.3.4. Loader.prototype.load(name[, referrer[, stage]])

The following steps are taken:

  1. Let loader be this value.
  2. If Type(loader) is not Object, throw a TypeError exception.
  3. If loader does not have all of the internal slots of a Loader Instance (3.5), throw a TypeError exception.
  4. If stage is undefined then let stageValue be "ready".
  5. Else let stageValue be ToString(stage).
  6. RejectIfAbrupt(stageValue).
  7. If IsValidStageValue(stageValue) is false, return a promise rejected with a new RangeError exception.
  8. Return the result of transforming Resolve(loader, name, referrer) with a fulfillment handler that, when called with argument key, runs the following steps:
    1. Let entry be EnsureRegistered(loader, key).
    2. Return LoadModule(entry, stageValue).

3.3.5. get Loader.prototype.registry

Loader.prototype.registry is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let loader be this value.
  2. If Type(loader) is not Object, throw a TypeError exception.
  3. If loader does not have all of the internal slots of a Loader Instance (3.5), throw a TypeError exception.
  4. Return loader.[[Registry]].

3.3.6. Loader.prototype [ @@toStringTag ]

The initial value of the @@toStringTag property is the String value "Object".

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

3.4. Properties of Loader Instances

Loader instances are ordinary objects that inherit properties from the *Loader.prototype*.

Loader instances are initially created with the internal slots described in the following table:

Internal Slot Value Type (non-normative) Description (non-normative)
[[Realm]] Realm Record The realm this loader belongs to.
[[Registry]] An object An instance of Registry.

4. Registry Objects

4.1. Abstract Operations for Registry Objects

4.1.1. CreateRegistry()

The abstract operation CreateRegistry with no arguments performs the following steps:

  1. Let O be ? OrdinaryCreateFromConstructor(Registry, "%RegistryPrototype%", «[[RegistryMap]]» ).
  2. Let M be ObjectCreate(%MapIteratorPrototype%, «[[Map]], [[MapNextIndex]], [[MapIterationKind]]»).
  3. Set O’s [[RegistryMap]] internal slot to M.
  4. Return O.

4.2. The Registry Constructor

The Registry constructor is the %Registry% intrinsic object. When called as a constructor it creates and initializes a new Registry object. When Registry is called as a function rather than as a constructor, it throws an exception.

4.3. Properties of the Registry Constructor

The value of the [[Prototype]] internal slot of the Registry constructor is the intrinsic object %FunctionPrototype%.

Besides the internal slots and the length property (whose value is 0), the Registry constructor has the following properties:

4.3.1. Registry.prototype

The value of Registry.prototype is %RegistryPrototype%.

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

4.4. Properties of the Registry Prototype Object

4.4.1. Registry.prototype.constructor

Registry.prototype.constructor is not intended to be called as a function or as a constructor and will always throw an exception.

Always throws to prevent the creation of new registry objects in user-land.

4.4.2. Registry.prototype[ @@iterator ]()

The initial value of the @@iterator property is the same function object as the initial value of the entries property.

The value of the name property of this function is "[Symbol.iterator]".

4.4.3. Registry.prototype.entries()

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. Let M be registry.[[RegistryMap]].
  5. Return CreateMapIterator(M, "key+value").

4.4.4. Registry.prototype.keys()

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. Let M be registry.[[RegistryMap]].
  5. Return CreateMapIterator(M, "key").

4.4.5. Registry.prototype.values()

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. Let M be registry.[[RegistryMap]].
  5. Return CreateMapIterator(M, "value").

4.4.6. Registry.prototype.get(key)

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. Let M be registry.[[RegistryMap]].
  5. Let entries be the List that is the value of M’s [[MapData]] internal slot.
  6. Repeat for each Record {[[key]], [[value]]} p that is an element of entries,
    1. If p.[[key]] is not empty and SameValueZero(p.[[key]], key) is true, return p.[[value]].
  7. Return undefined.

4.4.7. Registry.prototype.set(key, entry)

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. If Type(entry) is not Object, throw a TypeError exception.
  5. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), throw a TypeError exception.
  6. Let M be registry.[[RegistryMap]].
  7. Let entries be the List that is the value of M’s [[MapData]] internal slot.
  8. Repeat for each Record {[[key]], [[value]]} p that is an element of entries,
    1. If p.[[key]] is not empty and SameValueZero(p.[[key]], key) is true, then
      1. Set p.[[value]] to entry.
      2. Return registry.
  9. Let p be the Record {[[key]]: key, [[value]]: entry}.
  10. Append p as the last element of entries.
  11. Return registry.

4.4.8. Registry.prototype.has(key)

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. Let M be registry.[[RegistryMap]].
  5. Let entries be the List that is the value of M’s [[MapData]] internal slot.
  6. Repeat for each Record {[[key]], [[value]]} p that is an element of entries,
    1. If p.[[key]] is not empty and SameValueZero(p.[[key]], key) is true, then, return true.
  7. Return false.

4.4.9. Registry.prototype.delete(key)

The following steps are taken:

  1. Let registry be this value.
  2. If Type(registry) is not Object, throw a TypeError exception.
  3. If registry does not have all of the internal slots of a Registry Instance (4.4), throw a TypeError exception.
  4. Let M be registry.[[RegistryMap]].
  5. Let entries be the List that is the value of M’s [[MapData]] internal slot.
  6. Repeat for each Record {[[key]], [[value]]} p that is an element of entries,
    1. If p.[[key]] is not empty and SameValueZero(p.[[key]], key) is true, then
      1. Set p.[[key]] to empty.
      2. Set p.[[value]] to empty.
      3. Return true.
  7. Return false.
The value empty is used as a specification device to indicate that an entry has been deleted. Actual implementations may take other actions such as physically removing the entry from internal data structures.

4.5. Properties of Registry Instances

Registry instances are ordinary objects that inherit properties from the %RegistryPrototype%.

Registry instances are initially created with the internal slots described in the following table:

Internal Slot Value Type (non-normative) Description (non-normative)
[[RegistryMap]] The Map object of pairs of String and module status. The registry of installed modules.

5. ModuleStatus Objects

5.1. Abstract Operations for ModuleStatus Objects

5.1.1. GetCurrentStage(entry)

The abstract operation GetCurrentStage with argument entry performs the following steps:

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let stages be entry.[[Pipeline]].
  3. Return the first element of stages.

5.1.2. IsValidStageValue(stage)

The abstract operation IsValidStageValue with argument stage performs the following steps:

  1. Assert: Type(stage) is String.
  2. If stage is "fetch", "translate", "instantiate", "satisfy", "link" or "ready", return true.
  3. Else return false.

5.1.3. GetStage(entry, stage)

The abstract operation GetStage with arguments entry and stage performs the following steps:

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Assert: Type(stage) is String.
  3. Let stages be entry.[[Pipeline]].
  4. For each element entry of stages, do
    1. If entry.[[Stage]] is equal to stage, then
      1. Return entry.
  5. Return undefined.

5.1.4. LoadModule(entry, stage)

The abstract operation LoadModule with arguments entry and stage performs the following steps:

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Assert: Type(stage) is String.
  3. If stage is "fetch", then:
    1. Return the result of transforming RequestFetch(entry) with a new pass-through promise.
  4. If stage is "translate", then:
    1. Return the result of transforming RequestTranslate(entry) with a new pass-through promise.
  5. If stage is "instantiate", then:
    1. Return the result of transforming RequestInstantiate(entry) with a fulfillment handler that, when called with argument entry, runs the following steps:
      1. If entry.[[Module]] is a Function object, return entry.[[Module]].
      2. Return undefined.
  6. If stage is "satisfy", then:
    1. Return the result of transforming RequestSatisfy(entry) with a fulfillment handler that, when called with argument entry, runs the following steps:
      1. If entry.[[Module]] is a Function object, return entry.[[Module]].
      2. Return undefined.
  7. If stage is "link", then:
    1. Return the result of transforming RequestLink(entry) with a fulfillment handler that returns undefined.
  8. If stage is "ready" or undefined, then:
    1. Return the result of transforming RequestReady(entry) with a fulfillment handler that, when called with argument entry, runs the following steps:
      1. Return GetModuleNamespace(entry.[[Module]]).
  9. Return a promise rejected with a new RangeError exception.

5.1.5. UpgradeToStage(entry, stage)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Assert: Type(stage) is String.
  3. Let pipeline be entry.[[Pipeline]].
  4. Let stageEntry be GetStage(entry, stage).
  5. If stageEntry is not undefined, then
    1. Repeat while the first element of pipeline is not equal to stageEntry
      1. Remove first element from pipeline.
The internal slot [[Pipeline]] of an entry can never be empty. Alternative, this algo can be implemented using functional programming techinques or a reverse cycle to avoid walking the entries twice.

An stage is a record with the following fields:

Internal Slot Value Type (non-normative) Description (non-normative)
[[Stage]] "fetch", "translate", "instantiate", "satisfy", "link", "ready" A constant value to indicating which phase the entry is at.
[[Result]] Promise or undefined A promise for the stage entry.

Each [[Stage]] value indicates the currently pending operation. If the [[Result]] field is *undefined*, the operation has not been initiated; if the [[Result]] field is a promise, the operation has been initiated but not completed. Once a stage completes, its Stage Entry is removed from the pipeline. The following table describes the intended purpose of each stage of the pipeline:

Value Description (non-normative)
"fetch" fetching the requested module (e.g. from a filesystem or network)
"translate" translating the fetched source (as via a preprocessor or compiler)
"instantiate" instantiating the translated source as a Module Record
"satisfy" satisfying the module’s dependency graph by instantiating all of its (direct or indirect) dependencies
"link" linking all imports and exports of the module’s dependency graph
"ready" fully loaded and initialized

5.2. The ModuleStatus Constructor

The ModuleStatus constructor is the %ModuleStatus% intrinsic object and the initial value of the Status property of the Reflect.Module object. When called as a constructor it creates and initializes a new ModuleStatus object. When ModuleStatus is called as a function rather than as a constructor, it throws an exception.

The ModuleStatus constructor is designed to be subclassable. It may be used as the value of an extends clause of a class definition. Subclass constructors that intend to inherit the specified ModuleStatus behaviour must include a super call to the ModuleStatus constructor to create and initialize the subclass instance with the corresponding internal slots.

5.2.1. ModuleStatus(loader, key[, module])

When ModuleStatus is called with arguments loader, key and module, the following steps are taken:

  1. If NewTarget is undefined, then throw a TypeError exception.
  2. If Type(loader) is not Object, throw a TypeError exception.
  3. If loader does not have all of the internal slots of a Loader Instance (3.5), throw a TypeError exception.
  4. Let keyString be ? ToString(key).
  5. If Type(module) is not Object, throw a TypeError exception.
  6. If module does not have all of the internal slots of a Module Instance (8.5), throw a TypeError exception.
  7. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%ModuleStatusPrototype%", «[[Loader]], [[Pipeline]], [[Key]], [[Module]], [[Metadata]], [[Dependencies]], [[Error]]» ).
  8. Let pipeline be a new List.
  9. If module exists, then
    1. Let result be a promise resolved with module.
    2. Add new stage entry record { [[Stage]]: "ready", [[Result]]: result } as a new element of the list pipeline.
  10. Else,
    1. Add new stage entry record { [[Stage]]: "fetch", [[Result]]: undefined } as a new element of the list pipeline.
    2. Add new stage entry record { [[Stage]]: "translate", [[Result]]: undefined } as a new element of the list pipeline.
    3. Add new stage entry record { [[Stage]]: "instantiate", [[Result]]: undefined } as a new element of the list pipeline.
    4. Add new stage entry record { [[Stage]]: "satisfy", [[Result]]: undefined } as a new element of the list pipeline.
    5. Add new stage entry record { [[Stage]]: "link", [[Result]]: undefined } as a new element of the list pipeline.
    6. Add new stage entry record { [[Stage]]: "ready", [[Result]]: undefined } as a new element of the list pipeline.
  11. Set O’s [[Loader]] internal slot to loader.
  12. Set O’s [[Pipeline]] internal slot to pipeline.
  13. Set O’s [[Key]] internal slot to keyString.
  14. Set O’s [[Module]] internal slot to module.
  15. Set O’s [[Metadata]] internal slot to undefined.
  16. Set O’s [[Dependencies]] internal slot to undefined.
  17. Set O’s [[Error]] internal slot to nothing.
  18. Return O.
A module status is associated to a loader instance at the time of its creation, the [[Loader]] backpointer reflects that. The optional second argument module allows to create a module status in a way that synchronously circumvents the asynchronous loading process. A module status can be set into multiple Registry instances.

5.3. Properties of the ModuleStatus Constructor

The value of the [[Prototype]] internal slot of the Registry constructor is the intrinsic object %FunctionPrototype%.

Besides the internal slots and the length property (whose value is 0), the ModuleStatus constructor has the following properties:

5.3.1. ModuleStatus.prototype

The value of ModuleStatus.prototype is %ModuleStatusPrototype%.

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

5.4. Properties of the ModuleStatus Prototype Object

5.4.1. ModuleStatus.prototype.constructor

The initial value of ModuleStatus.prototype.constructor is the intrinsic object %ModuleStatus%.

5.4.2. get ModuleStatus.prototype.stage

ModuleStatus.prototype.stage is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let entry be this value.
  2. If Type(entry) is not Object, throw a TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), throw a TypeError exception.
  4. Let stageEntry be GetCurrentStage(entry).
  5. Return stageEntry.[[Stage]].

5.4.3. get ModuleStatus.prototype.module

ModuleStatus.prototype.module is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let entry be this value.
  2. If Type(entry) is not Object, throw a TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), throw a TypeError exception.
  4. Return entry.[[Module]].

5.4.4. get ModuleStatus.prototype.error

ModuleStatus.prototype.error is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let entry be this value.
  2. If Type(entry) is not Object, throw a TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), throw a TypeError exception.
  4. Return entry.[[Error]].

5.4.5. get ModuleStatus.prototype.dependencies

ModuleStatus.prototype.dependencies is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let entry be this value.
  2. If Type(entry) is not Object, throw a TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), throw a TypeError exception.
  4. Let array be ArrayCreate(0).
  5. Let n be 0.
  6. For each pair in entry.[[Dependencies]], do:
    1. Let O be ObjectCreate(%ObjectPrototype%).
    2. Let requestNameDesc be the PropertyDescriptor{[[Value]]: pair.[[RequestName]], [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
    3. Let requestNameStatus be ? DefinePropertyOrThrow(O, "requestName", requestNameDesc).
    4. Let keyDesc be the PropertyDescriptor{[[Value]]: pair.[[Key]], [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
    5. Let keyStatus be ? DefinePropertyOrThrow(O, "key", keyDesc).
    6. Let moduleStatusDesc be the PropertyDescriptor{[[Value]]: pair.[[ModuleStatus]], [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
    7. Let moduleStatus be ? DefinePropertyOrThrow(O, "entry", moduleStatusDesc).
    8. Let status be ? CreateDataProperty(array, ? ToString(n), O).
    9. Increment n by 1.
  7. Return array.
The dependencies accessor provides access to the dependency graph, exposing each dependency’s requestName, key and entry reference.

5.4.6. ModuleStatus.prototype.load(stage)

The following steps are taken:

  1. Let entry be this value.
  2. If Type(entry) is not Object, return a promise rejected with a new TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), return a promise rejected with a new TypeError exception.
  4. If stage is undefined then let stageValue be "fetch".
  5. Else let stageValue be ToString(stage).
  6. RejectIfAbrupt(stageValue).
  7. If IsValidStageValue(stageValue) is false, return a promise rejected with a new RangeError exception.
  8. Return LoadModule(entry, stageValue).

5.4.7. ModuleStatus.prototype.result(stage)

The following steps are taken:

  1. Let entry be this value.
  2. If Type(entry) is not Object, return a promise rejected with a new TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), return a promise rejected with a new TypeError exception.
  4. Let stageValue be ToString(stage).
  5. RejectIfAbrupt(stageValue).
  6. If IsValidStageValue(stageValue) is false, return a promise rejected with a new RangeError exception.
  7. Let stageEntry be GetStage(entry, stageValue).
  8. RejectIfAbrupt(stageEntry).
  9. If stageEntry is undefined, return a promise resolved with undefined.
  10. If stageEntry.[[Result]] is undefined, return a promise resolved with undefined.
  11. Return the result of transforming stageEntry.[[Result]] with a new pass-through promise.

5.4.8. ModuleStatus.prototype.resolve(stage, result)

The following steps are taken:

  1. Let entry be this value.
  2. If Type(entry) is not Object, return a promise rejected with a new TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), return a promise rejected with a new TypeError exception.
  4. If Type(entry) is not Object, throw a new TypeError.
  5. Let stageValue be ToString(stage).
  6. RejectIfAbrupt(stageValue).
  7. If IsValidStageValue(stageValue) is false, return a promise rejected with a new RangeError exception.
  8. Let p0 be the result of transforming result with a new pass-through promise.
  9. Let p1 be the result of transforming p0 with a fulfillment handler that, when called with argument value, runs the following steps:
    1. Let stageEntry be GetStage(entry, stageValue).
    2. If stageEntry is undefined, throw a new TypeError.
    3. If stageEntry.[[Result]] is undefined, then
      1. Set stageEntry.[[Result]] to a promise resolved with value.
    4. Else,
      1. Fulfill stageEntry.[[Result]] with value.
    5. UpgradeToStage(entry, stageValue).
    6. Return value.
  10. Return p0.

5.4.9. ModuleStatus.prototype.reject(stage, error)

The following steps are taken:

  1. Let entry be this value.
  2. If Type(entry) is not Object, return a promise rejected with a new TypeError exception.
  3. If entry does not have all of the internal slots of a ModuleStatus Instance (5.5), return a promise rejected with a new TypeError exception.
  4. Let stageValue be ToString(stage).
  5. RejectIfAbrupt(stageValue).
  6. If IsValidStageValue(stageValue) is false, return a promise rejected with a new RangeError exception.
  7. Let p0 be the result of transforming error with a new pass-through promise.
  8. Let p1 be the result of transforming p0 with a fulfillment handler that, when called with argument value, runs the following steps:
    1. Let stageEntry be GetStage(entry, stageValue).
    2. If stageEntry is undefined, throw a new TypeError.
    3. If stageEntry.[[Result]] is undefined, then
      1. Set stageEntry.[[Result]] to a promise rejected with value.
    4. Else,
      1. Reject stageEntry.[[Result]] with value.
    5. UpgradeToStage(entry, stageValue).
    6. Return value.
  9. Return p0.

5.5. Properties of ModuleStatus Instances

ModuleStatus instances are ordinary objects that inherit properties from the %ModuleStatusPrototype%.

ModuleStatus instances are initially created with the internal slots described in the following table:

Internal Slot Value Type (non-normative) Description (non-normative)
[[Loader]] An object The loader this module status belongs to.
[[Pipeline]] A List A list whose elements are stage records.
[[Key]] A String A value that identifies what key was used to create the module status instance.
[[Metadata]] Object or undefined The metadata object passed through the pipeline.
[[Dependencies]] List of Records of the form {[[RequestName]]: String, [[Key]]: String, [[ModuleStatus]]: Module Status} . Table mapping unresolved names to their resolved key and module status entries.
[[Module]] Module Record or Function object or undefined The Module Record if the entry is ready, or a thunk if the entry is delayed; otherwise undefined.
[[Error]] Any or nothing An error that was encountered during one of the phases of the loading pipeline; nothing if no error has been encountered.

6. Loading Semantics

6.1. Auxiliary Operations

6.1.1. EnsureRegistered(loader, key)

  1. Assert: loader must have all of the internal slots of a Loader Instance (3.5).
  2. Assert: Type(key) is String.
  3. Let registry be loader.[[Registry]].
  4. Let pair be the entry in registry.[[RegistryMap]] such that pair.[[Key]] is equal to key.
  5. If pair exists, then:
    1. Let entry be pair.[[Value]].
  6. Else,
    1. Let entry be a new ModuleStatus(loader, key).
  7. Return entry.

6.1.2. Resolve(loader, name, referrer)

  1. Assert: loader must have all of the internal slots of a Loader Instance (3.5).
  2. Assert: Type(name) is String.
  3. Assert: Type(referrer) is String.
  4. Let hook be GetMethod(loader, @@resolve).
  5. Return the result of promise-calling hook(name, referrer).

6.1.3. ExtractDependencies(entry, optionalInstance, source)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let instance be ? Instantiation(entry.[[Loader]], optionalInstance, source).
  3. Let deps be a new empty List.
  4. If instance is a Module Record, then:
    1. Assert: instance is a Source Text Module Record.
    2. Set instance.[[ModuleStatus]] to entry.
    3. For each dep in instance.[[RequestedModules]], do:
      1. Append the record { [[RequestName]]: dep, [[Key]]: undefined, [[ModuleStatus]]: undefined } to deps.
  5. Set entry.[[Dependencies]] to deps.
  6. Set entry.[[Module]] to instance.
[[Key]] will identifies the dependency module status in the entry’s corresponding loader.

6.1.4. Instantiation(loader, result, source)

  1. Assert: loader must have all of the internal slots of a Loader Instance (3.5).
  2. If result is undefined, then return ParseModule(source).
  3. If IsCallable(result) is false then throw a new TypeError.
  4. Set result.[[Realm]] to loader.[[Realm]].
  5. Return result.

6.2. Loading Operations

6.2.1. RequestFetch(entry)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let fetchStageEntry be GetStage(entry, "fetch").
  3. If fetchStageEntry is undefined, return a promise resolved with undefined.
  4. If fetchStageEntry.[[Result]] is not undefined, return fetchStageEntry.[[Result]].
  5. Let hook be GetMethod(entry.[[Loader]], @@fetch).
  6. Let p0 be the result of promise-calling hook(entry, entry.[[Key]]).
  7. Let p1 be the result of transforming p0 with a new pass-through promise.
  8. Let p2 be the result of transforming p1 with a fulfillment handler that, when called with argument payload, runs the following steps:
    1. UpgradeToStage(entry, "translate").
  9. Set fetchStageEntry.[[Result]] to p1.
  10. Return p1.

6.2.2. RequestTranslate(entry)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let translateStageEntry be GetStage(entry, "translate").
  3. If translateStageEntry is undefined, return a promise resolved with undefined.
  4. If translateStageEntry.[[Result]] is not undefined, return translateStageEntry.[[Result]].
  5. Let hook be GetMethod(entry.[[Loader]], @@translate).
  6. Let p be the result of transforming RequestFetch(entry) with a fulfillment handler that, when called with argument payload, runs the following steps:
    1. Let p0 be the result of promise-calling hook(entry, payload).
    2. Let p1 be the result of transforming p0 with a new pass-through promise.
    3. Let p2 be the result of transforming p1 with a fulfillment handler that, when called with argument source, runs the following steps:
      1. UpgradeToStage(entry, "instantiate").
    4. Return p1.
  7. Set translateStageEntry.[[Result]] to p.
  8. Return p.

6.2.3. RequestInstantiate(entry)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let instantiateStageEntry be GetStage(entry, "instantiate").
  3. If instantiateStageEntry is undefined, return a promise resolved with undefined.
  4. If instantiateStageEntry.[[Result]] is not undefined, return instantiateStageEntry.[[Result]].
  5. Let hook be GetMethod(entry.[[Loader]], @@instantiate).
  6. Let p be the result of transforming RequestTranslate(entry) with a fulfillment handler that, when called with argument source, runs the following steps:
    1. Let p0 be the result of promise-calling hook(entry, source).
    2. Let p1 be the result of transforming p0 with a new pass-through promise.
    3. Let p2 be the result of transforming p1 with a fulfillment handler that, when called with argument optionalInstance, runs the following steps:
      1. Let status be ? ExtractDependencies(entry, optionalInstance, source).
      2. UpgradeToStage(entry, "satisfy").
    4. Return p1.
  7. Set instantiateStageEntry.[[Result]] to p.
  8. Return p.

6.2.4. RequestSatisfy(entry)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let satisfyStageEntry be GetStage(entry.[[Loader]], "satisfy").
  3. If satisfyStageEntry is undefined, return a promise resolved with undefined.
  4. If satisfyStageEntry.[[Result]] is not undefined, return satisfyStageEntry.[[Result]].
  5. Let p be the result of transforming RequestInstantiate(entry) with a fulfillment handler that, when called with argument entry, runs the following steps:
    1. Let depLoads be a new empty List.
    2. For each pair in entry.[[Dependencies]], do:
      1. Let p be the result of transforming Resolve(loader, pair.[[Key]], key) with a fulfillment handler that, when called with value depKey, runs the following steps:
        1. Let depEntry be EnsureRegistered(entry.[[Loader]], depKey).
        2. Let pair.[[Key]] to depKey.
        3. Let pair.[[ModuleStatus]] to depEntry.
        4. Let currentStageEntry be GetCurrentStage(entry).
        5. If currentStageEntry.[[Stage]] is "ready", then:
          1. Return depEntry.[[Module]].
        6. Return the result of transforming RequestSatisfy(depEntry) with a fulfillment handler that, when called with value depEntry, runs the following steps:
          1. Return depEntry.[[Module]].
      2. Append p to depLoads.
    3. Let p be the result of waiting for all depLoads.
    4. Return the result of transforming p with a fulfillment handler that, when called, runs the following steps:
      1. UpgradeToStage(entry, "link").
      2. Return entry.
  6. Set satisfyStageEntry.[[Result]] to p.
  7. Return p.
This abstract operation completes the [[Dependencies]] registry per entry keeping backpointers to each dependency entry and the corresponding keys.
  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let linkStageEntry be GetStage(entry, "link").
  3. If linkStageEntry is undefined, return a promise resolved with undefined.
  4. If linkStageEntry.[[Result]] is not undefined, return linkStageEntry.[[Result]].
  5. Return the result of transforming RequestSatisfy(entry) with a fulfillment handler that, when called with argument entry, runs the following steps:
    1. Assert: entry’s whole dependency graph is in "link" or "ready" stage.
    2. Let status be ? Link(entry).
    3. Assert: entry’s whole dependency graph is in "ready" stage.
    4. Return entry.

6.2.6. RequestReady(entry)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let currentStageEntry be GetCurrentStage(entry).
  3. If currentStageEntry.[[Stage]] is equal "ready", return currentStageEntry.[[Result]].
  4. Return the result of transforming RequestLink(entry) with a fulfillment handler that, when called with argument entry, runs the following steps:
    1. Let module be entry.[[Module]].
    2. Let status be ? module.ModuleEvaluation().
    3. Return module.

7. Linking Semantics

7.1. Resolving Dependencies

7.1.1. HostResolveImportedModule(module, requestName)

The modules spec should only invoke this operation from methods of Source Text Module Records, and this spec does not invoke the operation at all.

  1. Assert: module is a Source Text Module Record.
  2. Assert: Type(requestName) is String.
  3. Let entry be module.[[ModuleStatus]].
  4. Let currentStageEntry be GetCurrentStage(entry).
  5. Assert: currentStageEntry.[[Stage]] is in "link" or "ready" stage.
  6. Let pair be the pair in entry.[[Dependencies]] such that pair.[[RequestName]] is equal to requestName.
  7. Assert: pair is defined.
  8. Let depEntry be pair.[[ModuleStatus]].
  9. Let depStageEntry be GetCurrentStage(depEntry).
  10. Assert: depStageEntry.[[Stage]] is equal "link" or "ready" stage.
  11. Return depEntry.[[Module]].

7.2. Linking

  1. Assert: root must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let deps be DependencyGraph(root).
  3. For each dep in deps, do:
    1. Let depStageEntry be GetCurrentStage(dep).
    2. If depStageEntry.[[Stage]] is "link" and dep.[[Module]] is a Function object, then:
      1. Let f be dep.[[Module]].
      2. Let m be ? f().
      3. Set dep.[[Module]] to m.
      4. UpgradeToStage(dep, "ready").
  4. Assert: the following sequence is guaranteed not to run any user code.
  5. For each dep in deps, do:
    1. Let depStageEntry be GetCurrentStage(dep).
    2. If depStageEntry.[[Stage]] is "link", then:
      1. Let module be dep.[[Module]].
      2. Assert: module is a Module Record.
      3. Let status be ? module.ModuleDeclarationInstantiation().
      4. UpgradeToStage(dep, "ready").
  6. Return undefined.

7.2.2. DependencyGraph(root)

  1. Assert: root must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Let result be a new empty List.
  3. Call ComputeDependencyGraph(root, result).
  4. Return result.

7.2.3. ComputeDependencyGraph(entry, result)

  1. Assert: entry must have all of the internal slots of a ModuleStatus Instance (5.5).
  2. Assert: result must be a List.
  3. If entry is already in result, then return undefined.
  4. Append entry to result.
  5. For each pair in entry.[[Dependencies]], do:
    1. Assert: pair.[[ModuleStatus]] is defined.
    2. Call ComputeDependencyGraph(pair.[[ModuleStatus]], result).
  6. Return undefined.

8. Module Objects

8.1. Reflective Module Records

A reflective module record is a kind of module record. It extends

Internal Slot Value Type (non-normative) Description (non-normative)
[[LocalExports]] A List of Strings The set of exported names stored in this module’s environment.
[[IndirectExports]] A List of pairs of String and {[[module]]: Module Record, [[bindingName]]: String}. The set of re-exported bindings. This ensures that ResolveExport can fully resolve re-exports.
[[Evaluate]] A function object or undefined A thunk to call when the the module is evaluated, or undefined if the module is already evaluated.

-

8.2. Abstract Operations for Module Objects

8.2.1. ParseExportsDescriptors(obj)

TODO: parse as in these examples

  1. // TODO: spec me

8.2.2. CreateModuleMutator(module)

  1. // TODO: spec me

8.2.3. GetExportNames(exportStarStack)

The following steps are taken:

  1. Let module be this Reflective Module Record.
  2. Let exports be a new empty List.
  3. For each name in module.[[LocalExports]], do:
    1. Append name to exports.
  4. For each pair in module.[[IndirectExports]], do:
    1. Append pair.[[Key]] to exports.
  5. Return exports.

8.2.4. ResolveExport(exportName, resolveStack, exportStarStack)

  1. Let module be this Reflective Module Record.
  2. If resolveStack contains a record r such that r.[[module]] is equal to module and r.[[exportName]] is equal to exportName, then
    1. Assert: this is a circular import request.
    2. Throw a SyntaxError exception.
  3. Append the record {[[module]]: module, [[exportName]]: exportName} to resolveStack.
  4. Let exports be module.[[LocalExports]].
  5. Let pair be the pair in exports such that pair.[[Key]] is equal to exportName.
  6. If pair is defined, then:
    1. Return the Record { [[module]]: module, [[bindingName]]: exportName }.
  7. Let exports be module.[[IndirectExports]].
  8. Let pair be the pair in exports such that pair.[[Key]] is equal to exportName.
  9. If pair is defined, then return pair.[[Value]].
  10. Return null.

8.2.5. ModuleDeclarationInstantiation()

Reflective modules are always already instantiated.

  1. Return undefined.

8.2.6. ModuleEvaluation()

  1. Let module be this Reflective Module Record.
  2. Let evaluate be module.[[Evaluate]].
  3. Set module.[[Evaluate]] to undefined.
  4. Return evaluate().

8.3. The Module Constructor

The Module constructor is the initial value of the Module property of the the Reflect object. When called as a constructor it creates and initializes a new Module object. Module is not intended to be called as a function and will throw an exception when called in that manner.

The Module constructor is designed to be subclassable. It may be used as the value in an extends clause of a class definition. Subclass constructors that intend to inherit the specified Module behaviour must include a super call to the Module constructor to create and initialize the subclass instance with the internal stage necessary to integrated with loaders.

8.3.1. Module(descriptors[, executor[, evaluate]])

When Module is called with arguments descriptors, executor, and evaluate, the following steps are taken:

  1. Let realm be the current Realm.
  2. Let env be NewModuleEnvironment(realm.[[globalEnv]]).
  3. Let exportDescriptors be ParseExportsDescriptors(descriptors). // TODO: interleave the subsequent loop with parsing?
  4. Let localExports be a new empty List.
  5. Let indirectExports be a new empty List.
  6. Let exportNames be a new empty List.
  7. Let envRec be env’s environment record.
  8. For each desc in exportDescriptors, do:
    1. Let exportName be desc.[[Name]].
    2. Append exportName to exportNames.
    3. If desc is an Indirect Export Descriptor, then:
      1. Let otherMod be desc.[[Module]].
      2. Let resolution be ? otherMod.ResolveExport(desc.[[Import]], « »).
      3. If resolution is null, then throw a SyntaxError exception.
      4. Append the record {[[Key]]: exportName, [[Value]]: resolution} to indirectExports.
    4. Else,
      1. Append exportName to localExports.
      2. If desc is an Immutable Export Descriptor, then:
        1. Let status be envRec.CreateImmutableBinding(exportName, true).
        2. Assert: status is not an abrupt completion.
      3. Else,
        1. Assert: desc is a Mutable Export Descriptor.
        2. Let status be envRec.CreateMutableBinding(exportName, false).
        3. Assert: status is not an abrupt completion.
      4. If desc.[[Initialized]] is true, then:
        1. Call envRec.InitializeBinding(exportName, desc.[[Value]]).
  9. If evaluate is undefined, then let evaluated be true. Otherwise let evaluated be false.
  10. Let mod be a new Reflective Module Record {[[Realm]]: realm, [[Environment]]: env, [[Namespace]]: undefined, [[Evaluated]]: evaluated, [[LocalExports]]: localExports, [[IndirectExports]]: indirectExports, [[Evaluate]]: evaluate}.
  11. Let ns be ModuleNamespaceCreate(mod, realm, exportNames).
  12. Set mod.[[Namespace]] to ns.
  13. If executor is not undefined, then
    1. Let mutator be CreateModuleMutator(mod).
    2. Let status be ? executor(mutator, ns).
  14. Return ns.

8.4. Properties of the Module Constructor

The value of the [[Prototype]] internal slot of the Module constructor is the intrinsic object %FunctionPrototype%.

Besides the internal slots and the length property (whose value is 0), the Loader constructor has the following properties:

8.4.1. Module.evaluate(m)

TODO: way to force evaluation of a module namespace exotic object (Reflect.Module.evaluate(m)? m[Reflect.Module.evaluate]()?)

8.4.2. Module.prototype

The value of Module.prototype is an ordinary object with a null [[Prototype]].

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

8.5. Properties of Module Instances

Module instances are module namespace exotic objects.

9. Local Loading

TODO:

10. Browser Loader

Every host environment must implement a default loader object as the initial value of the loader property of the System object.

10.1. System.loader Object

The Default Browser Loader Object is an %BrowserLoader% instance, whose internal slots are set as if it had been constructed by the expression Construct(%BrowserLoader%).

11. BrowserLoader Objects

11.1. The BrowserLoader Constructor

The BrowserLoader constructor is the %BrowserLoader% intrinsic object. When called as a constructor it creates and initializes a new BrowserLoader object. When BrowserLoader is called as a function rather than as a constructor, it throws an exception.

11.2. Properties of the BrowserLoader Constructor

The value of the [[Prototype]] internal slot of the BrowserLoader constructor is the intrinsic object %FunctionPrototype%.

Besides the internal slots and the length property (whose value is 0), the BrowserLoader constructor has the following properties:

11.2.1. BrowserLoader.prototype

The value of BrowserLoader.prototype is %BrowserLoaderPrototype%.

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

11.3. Properties of the BrowserLoader Prototype

11.3.1. BrowserLoader.prototype[ @@resolve ](name, referrer)

When the @@resolve method is called, the following steps are taken:

TODO: name resolution policy

The value of the name property of this function is "[Reflect.Loader.resolve]".

11.3.2. BrowserLoader.prototype[ @@fetch ](entry, key)

When the @@fetch method is called, the following steps are taken:

TODO:

The value of the name property of this function is "[Reflect.Loader.fetch]".

11.3.3. BrowserLoader.prototype[ @@translate ](entry, payload)

When the @@translate method is called, the following steps are taken:

TODO: no-op.

The value of the name property of this function is "[Reflect.Loader.translate]".

11.3.4. BrowserLoader.prototype[ @@instantiate ](entry, source)

When the @@instantiate method is called, the following steps are taken:

TODO:

The value of the name property of this function is "[Reflect.Loader.instantiate]".

Annexes

List of Well-Known Intrinsic Objects

The intrinsics are listed in table below:

Intrinsic Name Description (non-normative)
%Loader% The loader constructor (3.2)
%LoaderPrototype% The initial value of the *prototype* data property of %Loader% (3.3.1)
%BrowserLoader% The browser loader constructor (10.1)
%BrowserLoaderPrototype% The initial value of the *prototype* data property of %BrowserLoader% (10.2.1)
%Registry% The registry constructor (4.1)
%RegistryPrototype% The initial value of the *prototype* data property of %Registry% (4.3.1)
%ModuleStatus% The module status constructor (5.2)
%ModuleStatusPrototype% The initial value of the *prototype* data property of %ModuleStatus% (5.3.1)

Conformance

All diagrams, examples, and notes in this specification are non-normative, as are all sections explicitly marked non-normative. Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this specification are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this specification. [RFC2119]

Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant.)

Index

Terms defined by this specification

References

Normative References

[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119