JavaScript needs modules!, ctd

I generally try to make my slides visual aids instead of a crib sheet, which means they aren’t typically self-contained. So I should provide a little context for my earlier post on the Harmony module system.

First things first: this design has been a collaboration with Sam Tobin-Hochstadt, who’s been utterly indispensable, as well as discussions with the TC39 committee and the es-discuss mailing list.

Second things second: we are not in competition with CommonJS, nor do we intend to replace it! CommonJS has done great work building a framework for creating reusable components in JavaScript. Now, they didn’t have the luxury of changing the language. We do, and we have a responsibility to make use of it to make JavaScript — and the entire open web — a better development environment.

One of the best practices on the web is Doug Crockford’s “module pattern,” which is a coding pattern for writing unobtrusive components that avoid stepping on the toes of other components. Design patterns are a recurring theme on the web: innovative users find workarounds to the limitations of web technologies, and as these patterns of use become popular, browser vendors and standards organizations can and should provide built-in support to ultimately alleviate the need for the workarounds altogether.

So it should be for modules! Instead of having to go through the pain of creating and immediately calling functions in order to get better scoping semantics, and to save and restore global bindings to be unobtrusive, JavaScript should have better scoping and modularity from the outset.

The modules in this design are second-class, that is, they are a static declaration form rather than a general expression form. This means that the imports and exports of a module are known statically to the compiler, which makes them more amenable to supporting lexical scope.

The general forms for using modules are simple and intuitive: a module declaration binds a module and the export form determines what definitions within it are shared with the outside world. You can access the exports of another module simple by selecting them with the usual dot-syntax (such as UI.Widget), or you can bind local variables to the exports of another module with the import syntax.

Modules can have cyclic dependencies, as long as the modules are all in scope with one another — much like function declarations.

These basic, simple features are part of the theme of usability and minimal intrusion into the development process: putting code in a module should be as convenient a refactoring step as possible.

The next theme of the design is moving JavaScript towards true lexical scope. Previously, JavaScript has always had a global object at the top of the “scope chain” (i.e., the lexical environment), which means that undeclared variables are looked up dynamically in a shared object. This means that the language’s default behavior is to create global variables. This is easy to trip over, and makes it inconvenient to write modular programs.

The TC39 committee has already decided that the next version of JavaScript will make some backwards-incompatible semantic changes to the language — within reason — and only be enabled by opting in to a new language version. We’ve also agreed that ES5’s strict mode will be a baseline, i.e., the default semantics, for the new version. This eliminates two of the pieces of JavaScript that currently break lexical scope: with-statements and eval modifying its caller’s scope. The last remaining offender is the global object. With this new language version, we can finally eliminate that, too. The benefit is that JavaScript becomes lexically scoped at last — no more runtime errors due to accidentally-omitted var declarations or misspelled variable names!

Module declarations are scoped just like other declarations. They can be declared inline, they can be simple renamings of existing modules, or they can be loaded from external files. This latter form has much the same semantics as a script tag, except that it can be done from within JavaScript.

Notice that the client gets to name the module it loads, instead of the module naming itself. This is critical: it means that modules don’t have to try to contend for a global namespace and use painful naming techniques for defensively avoiding stepping on each other’s toes. They can also contain nested modules and happily name them whatever they like, without contention.

The last piece of the design is dynamic loading. Just because modules are second-class does not mean they cannot be dynamically represented as values. In fact, module instances are easily reflected as objects simply by referring to them in expressions. Module instance objects are essentially immutable “views” of module instances; you cannot modify the exports of a module instance, but you can dynamically query their properties just like any object.

Module loaders give you the ability to dynamically load modules, and even to create multiple isolated contexts in which to load them. You can even provide low-level hooks to create custom loading semantics for a given module loader. Dynamic loading makes it possible to achieve important web use cases like dynamic feature detection, conditional loading, or on-demand loading. At the more intricate end of the spectrum, you can do live upgrades, implement isolated and stratified execution environments like online IDE’s, and write security-critical code that runs untrusted code in restricted contexts.

In all, the point of our module proposal is to make it incredible easy to create JavaScript components and share them, and to make JavaScript a truly lexically scoped language. In a larger sense, the goal is to continue making it easier to share code, because when we lower the barriers to entry, more people do more awesome things with the web all the time. And that’s why we’re here.

Comments are closed.