The primary purpose of this spec is to provide better modularity mechanisms for packaging, deploying, and sharing ECMAScript code.
| Goals for language: | Goals for hosts: | Goals that are out of scope: | Non-goals: |
|---|---|---|---|
| Eliminate global object | Zero-admin provisioning | Security mechanisms and idioms | Primitives to enable experimentation with module systems |
| Orthogonality with existing features | Uniform location and retrieval | State isolation | Fully first-class modules (e.g., module expressions) |
| Compatibility with non-module code | Service isolation mechanisms | Multiple layers of abstraction | |
| Moving ES to true lexical scope | Multiple instantiations | Definition by reflective evaluation, esp. via string concatenation | |
| Simplicity and usability | Dynamic upgrade | Definition by implementation | |
| Smooth refactoring path from non-module code | |||
| Preserve web UX of online execution, interactive perf | |||
| Standard mechanism for code sharing | |||
| Definition by semantic specification |
See the simple modules examples page for some highlights.
This proposal describes an incompatible semantics, in the sense that there are ES5 programs that cannot be used unchanged with the new semantics. For compatibility, some sort of versioning is required to indicate that code is evaluated with the new semantics.
The semantics implicitly enables ES5 strict mode and lexical scope mode. In particular, eval cannot modify its container’s scope, with is disallowed, and there is no global object on the scope chain. The semantics ensures that there are never any object environment records on the scope chain except in using eval to evaluate legacy code.
The host environment may provide extra-linguistic means, such as compilation switches or language dialects (again, see versioning) to import certain standard libraries by default.
script tags on a web page, in the order they appear.)module, var, function, etc.) and statements. (On the web, this corresponds to a single script tag.)module M { ... } construct.
ExportDeclaration ::= 'export' VariableStatement
| 'export' FunctionDeclaration
ImportDeclaration ::= 'import' ModuleSpecifier '.' '*' ';'
| 'import' ModuleSpecifier '.' ImportSpecifier ';'
ModuleSpecifier ::= Identifier
| ModuleSpecifier '.' Identifier
ImportSpecifier ::= '{' ImportSpecifierItem*(',') '}'
ImportSpecifierItem ::= Identifier (':' Identifier)?
ModuleDeclaration ::= 'module' Identifier '=' 'load' MRL+(',') ';'
| 'module' Identifier '=' ModuleSpecifier ';'
| 'module' Identifier '{' ModuleElement* '}'
MRL ::= StringLiteral
| '(' MRL ')'
| MRL '||' MRL
ModuleElement ::= Statement
| FunctionDeclaration
| ImportDeclaration
| ExportDeclaration
| ModuleDeclaration
Script ::= ScriptElement*
ScriptElement ::= Statement
| FunctionDeclaration
| ImportDeclaration
| ModuleDeclaration
The initial scope chain of an application consists of a global environment record, which contains only module bindings. The global environment record can acquire new entries at runtime, since the module loader may dynamically register new global modules. All subsequent frames in the scope chain are declarative environment records; the global environment record is effectively the only dynamically changing record.
The global object is essentially gone. It is only available via an accessor in the current module loader, or through evaluation of legacy code via eval.
A program is a sequence of scripts, evaluated from top to bottom. Each script is evaluated in a nested declarative environment record. Note: This means that subsequent scripts may refer to bindings in previous scripts, but not vice versa. This precludes mutual recursion between multiple scripts, but this means that scripts can be incrementally evaluated, each separately compiled.
At the top level of an individual script, variable and function declarations are hoisted to that script’s declarative environment record.
After a script is compiled, and before it begins evaluation, all of the script’s top-level modules are attached to the current module loader, and consequently accessible via the global environment record. This allows dynamic access to all top-level modules in all scripts. (Note that this does not make scripts mutually recursive, since these bindings are only attached after compilation.)
In the initial scope of a module, the only bindings in scope are the declarative module bindings in its scope chain. The current module loader determines what modules, if any, are in the global scope by default.
In the initial scope of a module, the binding of this is to the instance object of that module.
Performance note: to avoid unnecessary space consumption, closures should not capture any non-module bindings from the portion of the scope chain outside the current module.
The only dynamic portion of the scope chain is the global environment record. Each script is compiled in the context of a current state of the global environment record. If a script contains any free variables, a compile-time error is reported and the script is not evaluated.
Modules can be lexically nested, but are static declaration forms, not expression forms.
Modules are bound in the same lexical environment as variables and functions, i.e., in the scope chain.
It is a static error to declare a binding with the same name as a module binding at the same level of scope, e.g.:
{ module m { ... } function m() { ... } // error }
Module name resolution is simply lookup in the scope chain.
Each static module has a mapping from its nested module declarations to their corresponding static modules.
Dotted module name resolution selects nested modules, which involves a statically computable lookup.
Compatibility note: object environment records never contain bindings to static modules. If an object environment record happens to contain a module property (e.g., because a module object flowed into legacy non-strict code), it contains no static information. Note that this is harmless since non-strict code cannot do static imports.
Pragmatics note: nested modules make the “reverse DNS” naming convention (as used in Java) unnecessary; in fact, it doesn’t even work, since you can’t create an open, extensible module called e.g. org or com. Library writers can simply give their library a single identifier name such as jQuery. If clients need two different modules with the same name, they can disambiguate by placing them in different parent modules.
Imports can only appear at the outermost scope of a module or script body. Their bindings are in scope for their entire containing block.
It is a static error for two import declarations or an import declaration and another kind of declaration to attempt to bind the same identifier in the same scope.
Imported bindings may not be mutated within the module. Because all module exports are write-once, it is possible for a module to import a binding that has not yet been set (which results in an exception). However, once an imported binding has been initialized, its value will never change.
ImportDeclaration ::= ‘import’ ModuleSpecifier ‘.’ ‘*’ ‘;’ Binds all the exported names from the specified module in the current scope.
ImportDeclaration ::= ‘import’ ModuleSpecifier ‘.’ ImportSpecifier ‘;’ Binds the identifiers specified in the import specifier, in the current scope, to the corresponding exports from the specified module.
ModuleSpecifierItem ::= Identifier ‘:’ Identifier Binds the export specified by the first identifier locally with the name specified by the second identifier.
ModuleSpecifierItem ::= Identifier Binds the specified export.
ExportDeclaration ::= ‘export’ (VariableStatement | FunctionDeclaration)
Exports the binding(s) from this statement. The bindings are const, i.e., write-once.
A module may refer to or import from (and thus depend on) any other module in scope. This allows cyclic dependencies between any two modules in a single scope (such as the top-level scope) by virtue of being in scope of one another.
Module instance objects are a runtime reflection of a module instance as a first-class object. Instance objects are almost frozen, except that their properties may internally change from undefined to defined during the initial evaluation of their corresponding module instance, when it initially assigns to its exports. But their properties cannot be explicitly updated in importing code, they cannot receive new properties, etc. If an own-property is read before its corresponding export has been defined, an exception is thrown.
A module instance object has a prototype chain, which ultimately leads to the Object prototype (possibly going through some prototype object with standard methods for module instance objects).
The own-properties of a module instance object are precisely its exports (including nested modules).
Determining whether a module instance object has a particular export is possible via hasOwnProperty.
A module body is evaluated in textual order (from top to bottom).
If a module export is dereferenced before it is initialized, an exception is thrown.
The current module loader determines how to interpret MRL’s. The disjunction syntax:
MRL || MRL
informs the module loader to try each MRL in turn until one can successfully retrieve its module source.
Dynamic module loading is possible via module loaders.
Host environments may provide a binding through the global object to give legacy scripts convenient dynamic access to the current module loader. On the web, this would enable patterns such as:
<button onclick="modules.MyApp.handleClick(this)">Click Me</button>
package p { ... }package p;<script type="text/javascript?version=1.5" module="jQuery" bindings="this.jQuery" />