ECMAScript 6 modules in future browsers

[2013-11-19] esnext, dev, javascript, jsmodules, clientjs
(Ad, please don’t block)
Update 2013-11-22: David Herman has published the slide deck “Status Report: ES6 Modules”.

[1] is an introduction to ECMAScript 6 modules and how they can be used in current browsers. In contrast, this blog post explains how future browsers will support them natively. As part of that support, we will get the <module> tag, a better version of the <script> tag.

Browsers: asynchronous modules, synchronous scripts

Modules. To be in line with JavaScript’s usual run-to-completion semantics, the body of a module must be executed without interruption. That leaves two options for importing modules:
  1. Load modules synchronously, while the body is executed. That is what Node.js does.
  2. Load all modules asynchronously, before the body is executed. That is how AMD modules are handled. It is the only option for browsers, because modules are loaded over the internet. Otherwise, execution would pause for too long. As an added benefit, this approach allows one to load multiple modules in parallel.

Scripts. Scripts are normally loaded or executed synchronously. The JavaScript thread stops until the code has been loaded or executed.

ECMAScript 6 modules in browsers

ECMAScript 6 gives you the best of both worlds: The synchronous syntax of Node.js plus the asynchronous loading of AMD. To make both possible, the ECMAScript 6 modules are syntactically less flexible than Node.js modules: Imports and exports must happen at the top level. That means that they can’t be conditional, either. This restriction allows an ECMAScript 6 module loader to analyze statically what modules are imported by a module and load them before executing its body.

Given the synchronicity of scripts, it is obvious that you can’t simply add import and export capability and turn them into modules. There must be a way to handle module code differently. Therefore, there will be a new tag <module> for modules that replaces the <script> tag and is completely asynchronous:

    <module>
        import $ from 'lib/jquery';
        var x = 123;

        // The current scope is not global
        console.log('$' in window); // false
        console.log('x' in window); // false

        // `this` still refers to the global object
        console.log(this === window); // true
    </module>
As you can see, the tag has its own scope and variables “inside” it are local to that scope. Note that module code is implicitly in strict mode [2]. This is great news – no more 'use strict';

Similar to <script>, <module> can also be used to load external modules. For example, the following tag starts a web application via a main module (the attribute name import is my invention, it isn’t yet clear what name will be used).

    <module import="impl/main"><module>
For legacy browsers, the following tag is a polyfillable [3] alternative (either with inner text or an attribute import):
    <script type="module">

Bundling

Modern web applications consist of many, often small, modules. Loading those modules over HTTP impacts performance negatively, because a separate request is needed for each. Therefore, bundling multiple modules as a single file has a long tradition in the web development world. Current approaches are complex and error-prone and only work for JavaScript. Therefore, the W3C Technical Architecture Group is working on a new approach: Arbitrarily nested directories are archived as a single package file. Browsers access files in the package via a new kind of URL:
    url-for-package SEPARATOR path-inside-package
To support graceful degradation, the separator must be sent to the server by current browsers, must not be rejected by current servers and must not appear in current web content. Furthermore, package URL and separator must not impede the resolution of relative paths (including those with two dots in them). That means, for example, that neither of them can include a question mark, because that limits the resolution of relative paths to what comes before that symbol.

One candidate for the separator is "!/". With this separator, a URL that refers to a file main.js inside a package looks like this:

    http://example.com/app.pack!/impl/main.js
The following two tags use package URLs. The first starts a web application, the second displays a logo. In both cases, the files are inside the package.
    <script src="app.pack!/impl/main.js">
    <img src="app.pack!/img/logo.jpg">
The nice thing about packaging is that old servers and browsers can use it, too:
  • Old browsers send separate requests for each file in a package.
  • New browsers download the package once and afterwards extract files as necessary.
  • Old servers store both separate files and the package and serve either one, depending on what browsers request.
  • New servers could create the package on demand.
The proposed default package format is message/http with a body that is Multipart MIME. Thanks to that format, packages contain both files and meta-data (stored in HTTP headers). Packages can be compressed via gzip for transfer.

Intriguingly, packages could become a cross-browser format for archiving web pages (including images, CSS, etc.).

How to best set up modules to be loaded from a package is still being worked out. At the very least you can plug into the module loader API and set it up manually.

Conclusion

The main advantages of ECMAScript 6 modules compared to current module systems are: Their syntax is more elegant (than even Node.js modules), they work both synchronously and asynchronously (without an extra compilation step), and they unify the JavaScript module landscape.

In its inline version, the <module> tag nicely cleans up the problematic <script> tag:

  • Strict mode is on by default, avoiding the visual clutter of 'use strict';
  • The code runs asynchronously.
  • The code runs in its own scope, there is no danger of polluting the global scope.

Sources of this post

References

  1. ECMAScript 6 modules: the future is now
  2. JavaScript’s strict mode: a summary
  3. What is the difference between a shim and a polyfill?