Helma Logo
main list history
previous version  overview  next version

Version 5 by Kris Kowal on 06. September 2008, 20:26

from http://modulesjs.com/standard-proposal.html

The purpose of this document is to propose a contract between a collection of JavaScript module loaders and modules.  The specification describes the environment that module loader implementations provide, and the environment that modules may depend upon.  In particular, compliant module systems:

* map one file to one module and reserve names for implementation-specific multi-module bundling for website performance,
* memoize singleton module objects before executing the corresponding module file,
* execute modules with a featureful context,
* resolve module URLs relative to a module path, and
* conform to a domain-name-based site-package naming convention and leave a name-space for a centrally managed standard library.

The specification is intended to be suitable for client- and server-side JavaScript module loader implementations.

The specification is intended to provide insights and an easy migration path to future versions of JavaScript.

The specification is intended to describe a JavaScript execution context that conformant JavaScript modules can depend upon such that they may be used with all conformant module loaders.  This execution context could inform a "lint" implementation to verify conformance and permit additional variable renaming options for source code compressors like Packer, ShrinkSafe, and YUICompressor.

= Module Execution Context =

The singleton <tt>module</tt> object MUST be declared and memoized BEFORE the correpsonding module file is executed.  The module file MUST only be executed ONCE for the duration of a page-view or JavaScript program.

In a module file's execution context, the context object, represented by <tt>this</tt>, MUST be the <tt>module</tt> object.

The scope chain, from global to local, of a module file's execution context MUST consist of:

* <tt>global</tt>
* <tt>local</tt>
* <tt>module</tt>
* <i>&lt;anonymous&gt;</i>

== <tt>global</tt> ==

Rules for module systems:

* The <tt>global</tt> object MAY be frozen.
* All objects in the transitive closure of accessible through <tt>global</tt> on item selection MAY be frozen.
* The <tt>global</tt> object MAY contain more values properties than specified.
* The <tt>global</tt> object MUST include:
** <tt>String</tt>
** <tt>Number</tt>
** <tt>Boolean</tt>
** <tt>Object</tt>
** <tt>Array</tt>
** <tt>Date</tt>
** <tt>RegExp</tt>
** <tt>Error</tt>
** <tt>EvalError</tt>
** <tt>RangeError</tt>
** <tt>ReferenceError</tt>
** <tt>SyntaxError</tt>
** <tt>TypeError</tt>
* The module loader MAY enforce these invariants at any time.  In some environments, verifying these invariants will not be possible or pragmatic.
* All objects in <tt>global</tt> MUST conform to the JavaScript subset described in the introduction: one that consists of the intersection of behaviors of the respective objects in all Grade-A browsers and server-side JavaScript environments.

Rules for modules:

* Modules MUST NOT modify the <tt>global</tt> object.
* Modules MUST NOT modify any object accessible through references from <tt>global</tt>.
* Modules MUST NOT access any properties in the <tt>global</tt> object not herein specified.
* Modules MUST NOT use non-standard features provided by objects accessible from <tt>global</tt>.

== <tt>local</tt> ==

The <tt>local</tt> object is a module's private namespace for module loader functions and imported values.

The <tt>local</tt> object MUST provide:

* <tt>global</tt>
* <tt>module</tt>
* <tt>moduleUrl</tt>
* <tt>moduleRootUrl</tt>
* <tt>require</tt>
* <tt>include</tt>
* <tt>foreignModuleBind</tt>
* <tt>log</tt>

The <tt>local</tt> object MAY provide the following functions for web-site implementations that need to support multi-module bundling.  These functions would permit module constructor functions for more than one module file to be collected in a single file.

* <tt>register</tt>
* <tt>publish</tt>

Modules MAY augment the <tt>local</tt> object with additional items.

Modules MUST NOT replace properties specified here.

= Module Loading =

== <tt>require(</tt><i>&lt;moduleUrl&gt;</i><tt>, </tt><i>[&lt;structure&gt;]</i><tt>)</tt> ==

The <tt>require</tt> function returns an object with properties copied from a foreign module.  The URL refers to a file that could be executed to construct the module object.  By default, all properties from the foreign module are copied into the returned object.

The returned object MAY be frozen.

Modules MUST NOT modify the returned object.

If a <tt>structure</tt> is provided, a subset of the items from the foreign module will be returned, the result of <tt>destructure(</tt><i>&lt;module&gt;</i><tt>, </tt><i>&lt;structure&gt;</i><tt>)</tt>.  If a function in the foreign module was declared with the <tt>foreignModuleBind</tt> decorator, the corresponding item in the returned object is the result of <tt>bind(</tt><i>&lt;function&gt;</i><tt>, <tt><i>&lt;foreignModule&gt;</i><tt>)</tt>.

If the URL begins with a dot, ("."), the fully qualified URL for the requested module is resolved relative to the fully qualified URL of the current module.  This would be the result of <tt>urlJoin(moduleRootUrl, moduleUrl, foreignModuleUrl)</tt>.  Otherwise the fully qualified URL is <tt>urlJoin(moduleRootUrl, foreignModuleUrl)</tt>.

Regarding module file names:

* Directory names and file names in modules MUST be in <tt>camelCase</tt>.
* Modules MUST have a ".js" extension if they are provided by files.
* Modules MUST not have an extension if they are provided by the module loader but are not backed by real files.  This might include a "window" module in a particular browser implementation.
* The module root is reserved for a cross-browser JavaScript standard library.
* Modules provided by entities other than the standard library MUST exist in a subdirectory of the module root corresponding to a domain name controlled by the author, or a subdirectory thereof.

Module authors are encouraged to use module relative URLs to increase the mobility of entire directory trees of modules.

== <tt>include(</tt><i>&lt;moduleUrl&gt;</i><tt>, </tt><i>[&lt;structure&gt;]</i><tt>)</tt> ==

The <tt>include</tt> function defers to <tt>require</tt>, both its arguments and its return value.  However, <tt>include</tt> also copies all of the items from the object returned by <tt>require</tt> to the <tt>moduleScope</tt> object.

== <tt>foreignModuleBind(</tt><i>&lt;function&gt;</i><tt>)</tt> ==

A function decorator that denotes that the module loader will guarantees that, when the decorated function is called, it will receive the <tt>module</tt> object for the module file in which it was called as its context object: <tt>this</tt>.  <tt>foreignModuleBind</tt> MAY return the same <tt>Function</tt> it was passed.  The returned function MUST be usable in the module in which it was declared as if it were the function passed to <tt>foreingModuleBind</tt>.  <tt>foreignModuleBind</tt> MAY modify properties of the given <tt>Function</tt>.

For example:

  this.foo = foreignModuleBind(function () {
      log("foo called from: " + this.local.moduleUrl);

== <tt>destructure(</tt><i>&lt;object&gt;</i><tt>, </tt><i>&lt;structure&gt;</i><tt>)</tt> ==

For the purpose of this specification, the "destructure" function has the following semantics.  If the structure is an <tt>Array</tt>, the returned <tt>Object</tt> contains the properties from the given <i>&lt;object&gt;</i> corresponding to the keys provided in the given <tt>Array</tt> structure.  If the structure is an <tt>Object</tt>, the returned object contains properties where each key corresponds to a value in the structure, and the value is a value from the object corresponding to the key in the structure.  For example:

* <tt>destructure({"a": 10, "b": 20}, ["a"]) == {"a": 10}</tt>
* <tt>destructure({"a": 10, "b": 20}, {"a": "A"}) == {"A": 10}</tt>

== <tt>module</tt> ==

The <tt>module</tt> object is a module's public interface.  Adding items to the <tt>module</tt> object makes them available for export to other module <tt>local</tt> scopes.  To that end, the <tt>module</tt> object is mutable.  The <tt>module</tt> object MUST provide a <tt>local</tt> object.  Modules MAY augment the module object.

== <tt>moduleRootUrl</tt> ==

<tt>moduleRootUrl</tt> is the fully qualified URL of the JavaScript site-packages directory: the module root.

== <tt>moduleUrl</tt> ==

<tt>moduleUrl</tt> is the relative URL of the current module file relative to <tt>moduleRootUrl</tt>.

== <tt>print(</tt><i>&lt;message&gt;</i><tt>, </tt><i>[&lt;label&gt;]</i>)</tt> ==

<tt>print</tt> is a simple console logging function.

The module loader MAY ignore calls to <tt>print</tt>.  The module loader MAY ignore the label argument.

The optional label MAY be any string, and MUST be suitable for use as a CSS class-name (preferably lower-case delimited by hyphens) of which the following MAY be significant:

* info
* warn
* error
* module
* help
* pass
* fail

= Afterword: Browser Implementations =

This specification outlines the process of requiring modules from within other modules.  However, in a browser's global execution context, JavaScript execution blocks are not modules.  To that end, this specification does not require that the module loader be invoked in any particluar fashion.  A particular implementation might hook an initial module to be loaded from within a script tag.  Another implementation might scan the DOM for script tags with an alternate language attribute and execute them as modules with the current page's URL as their module URL.  This specification only exists to maximize the portability of module files.

= Afterword: ECMAScript <tt>import</tt> semantics. =

A future version of the ECMAScript standard might specify new syntax and semantics for importing modules.  Current discussions about this feature trend toward having new syntax that "desugars" to native JavaScript.  To that end, I propose the following syntax and desugaring transformations in the context of this specification.  The top line of each pair is akin to Python's import syntax and stands in for whatever syntax the next version of JavaScript divines.  The second line is an equivalent, desugared version using the module system specified here.

* <tt>import "</tt><i>&lt;moduleUrl&gt;</i><tt>" as </tt><i>&lt;moduleName&gt;</i>;</tt>
* <tt>module.</tt><i>&lt;moduleName&gt;</i><tt> = require("</tt><i>&lt;moduleUrl&gt;</i><tt>");</tt>

* <tt>from "</tt><i>&lt;moduleUrl&gt;</i><tt>" import *;</tt>
* <tt>include("</tt><i>&lt;moduleUrl&gt;</i><tt>");</tt>

* <tt>from "</tt><i>&lt;moduleUrl&gt;</i><tt>" import </tt><i>&lt;a&gt;</i><tt>;</tt>
* <tt>include("</tt><i>&lt;moduleUrl&gt;</i><tt>", ["</tt><i>&lt;a&gt;</i><tt>"]);</tt>

* <tt>from "</tt><i>&lt;moduleUrl&gt;</i><tt>" import </tt><i>&lt;a&gt;</i><tt> as </tt><i>&lt;a&apos;&gt;</i><tt>;</tt>
* <tt>include("</tt><i>&lt;moduleUrl&gt;</i><tt>", {"</tt><i>&lt;a&gt;</i><tt>": "</tt><i>&lt;a&apos;&gt;</i><tt>"});</tt>