This proposal describes a module system for ECMAScript. It describes the semantics of the core module system. See the module loaders spec for the companion proposal to provide a dynamic, reflective API for loading modules.
See the modules examples page for highlights of the module system.
See the modules rationale page for a description of the design rationale.
See the modules semantics page for a more detailed description of the semantics.
The basic syntax of ES programs is extended as follows (the Script and ScriptElement non-terminals replace the ES5 Program and ProgramElement):
Script ::= ScriptElement*
ScriptElement ::= Statement
| VariableDeclaration
| FunctionDeclaration
| ImportDeclaration
| ClassDeclaration
| ModuleDeclaration
ModuleDeclaration ::= "module" [NoNewline] StringLiteral "{" ModuleBody "}"
ModuleBody ::= ModuleElement*
ModuleElement ::= ExportDeclaration
| VariableDeclaration
| ClassDeclaration
| FunctionDeclaration
| ImportDeclaration
ExportDeclaration ::= "export" ExportSpecifierSet ("," ExportSpecifierSet)* ";"
| "export" VariableDeclaration
| "export" FunctionDeclaration
ExportSpecifierSet ::= "{" ExportSpecifier ("," ExportSpecifier)* "}"
| Identifier
| "*" ("from" Path)?
ExportSpecifier ::= Identifier (":" Path)?
Path ::= Identifier ("." Identifier)*
ModuleSpecifier ::= StringLiteral | Identifier
ImportDeclaration ::= "import" ImportClause ("," ImportClause)* ";"
ImportClause ::= StringLiteral "as" Identifier
| ImportSpecifierSet "from" ModuleSpecifier
ImportSpecifierSet ::= "{" ImportSpecifier ("," ImportSpecifier)* "}"
ImportSpecifier ::= Identifier (":" Identifier)?
import "crypto" as crypto; // binding an external module to a variable import { encrypt, decrypt } from "crypto"; // binding a module's exports to variables import { encrypt: enc } from "crypto"; // binding and renaming one of a module's exports export * from "crypto"; // re-exporting another module's exports export { foo, bar } from "crypto"; // re-exporting specified exports from another module
Module declarations can only appear at the top level of a program or module body. They are compiled and linked during the compilation of their containing program or module.
Modules can be declared inline:
module "foo" { export let x = 42; }
Modules can be loaded from external resources:
import "foo" as foo; import { y } from foo;
It is not necessary to bind a module to a local name, if the programmer simply wishes to import directly from the module:
import { y } from "foo";
The external module is fetched and compiled during the compilation of the loading module. (Depending on the current module loader, this may trigger user-defined compilation hooks. See module loaders for more information.)
External modules do not name themselves; rather, their files simply contain the contents of the module. This prevents wasteful indentation and allows clients to determine the most appropriate local name for the third-party libraries they load.
An external module is compiled and executed in a fresh scope chain that extends only the global scope. This means that external modules have access to standard bindings, but their own bindings do not modify the global scope (i.e., their top-level variables are local to the module.)
Depending on the module loader, multiple module URL references may resolve to a shared, single module instance. In this case, the first reference that is evaluated executes the module body, and subsequent references simply produce the same instance without re-executing the body.
Import declarations bind another module’s exports as local variables. Imported variables may be locally renamed to avoid conflicts. The * form imports all exports from a module.
The static variable resolution and linking pass checks for conflicts in imported variable names. If there is a conflict between the names from two distinct imports, and exactly one of the imports uses the * form, then the other import takes precedence. If there is a conflict between two explicitly named imports, then it is a compile-time error. If there is a conflict between two * imports, and the conflicting binding is referenced or exported, and the two imports refer to two different original declarations of the variable or module, then it is a static error.
Export declarations declare that a local binding at the top-level of a module is visible externally to the module. The set of exports of a module is fixed at the module’s compile-time. Other modules can read (get) the module exports but cannot modify (set) them. Exports can be renamed so that their external name is different from their local name.
Compilation resolves and validates all variable definitions and references. Linking also happens at compile-time; linking resolves and validates all module imports and exports.
At run-time, the program is evaluated top-down. Before the program body begins executing, all child modules are instantiated, which is a recursive operation that transitively instantiates all descendent modules. Module instantiation initializes all module top-level function bindings, and initializes all variable bindings to the undefined value. Each externally-required module is executed the first time a module binding requires it.
Modules are bound in the same scope chain as other bindings. At run-time, a reference to a module returns a module instance object, which is a run-time reflection of the module instance.
A module instance object is a prototype-less object that provides read-only access to the exports of the module. All of the exports are provided as getters without setters.
Reflective evaluation, via eval or the module loading API starts a new compilation and linking phase for the dynamically evaluated code. As in ES5, the direct eval operator inherits its caller’s scope chain.
The eval operator is a blocking API, so a host environment is permitted to reject programs passed to eval which require fetching code from external resources (see the module loading API for a definition of when code is fetched). This prevents the evaluated code from blocking on reads from external resources. (Host environments like node.js may instead choose to allow site-local URL‘s, but browsers would not.)
The initial binding of this is the global object.