Happy new year, everyone. I've got some good news.

There's been grumbling lately about the ES6 version opt-in [1]. The detractors 
are not right about everything, but it's true that both the MIME type opt-in:

    <script type="application/ecmascript;version=6">
        ...
    </script>

and the pragma opt-in:

    use version 6;

are painful -- ugly, verbose, and a permanent tax for opting to use the new 
semantics.

We can do better, and the way we can do it is with modules. We can introduce 
module syntax in a backwards-compatible way, and use them as the implicit 
opt-in for any new semantics [2].

And the benefit we get is **just one JavaScript**.

# Backwards-compatible syntax

Both `use` and `module` can be contextual keywords, with a restricted 
production that disallows a newline after the keyword. To avoid ambiguity with 
contextual infix operators like `is` and `isnt` [3], such as:

    module is {}

we can resolve in favor of the declaration form in statement context, and in 
any other expression contexts `module` is just an ordinary identifier.

This is totally backwards-compatible: all the new forms would have been parse 
errors in ES5:

    module foo { }
    module bar {
    }
    module {
    }

and in all cases that ASI accepts in ES5, it continues to parse the same, i.e., 
not as a module:

    module // semicolon inserted
    foo    // semicolon inserted

# Modules as the opt-in for new semantics

Any ES6 feature that breaks web-compatibility can be made available only within 
module code. For example, if `let` as a keyword breaks the web, then in global 
code we treat it as an identifier, but we treat it as a keyword once you're 
inside a module. Similarly, the proper but backwards-incompatible scoping 
semantics for block-local functions would only be enabled within module code.

We could do the same for `yield`, but since it's only meaningful within 
function*, we should allow generator functions outside of modules as well. I'm 
not sure whether we can/should reserve `yield` as a keyword in global code.

Each new feature can be considered independently. Anything that is only allowed 
within modules becomes a carrot to lead programmers to the improved semantics, 
but whenever we can compatibly make something available to global code, we 
should do so. No point needlessly depriving programmers of ES6 goodies such as 
destructuring or spread/rest.

# What about eliminating the window from the scope chain?

Short answer: giving up.

You can still do it with a custom loader, and I still want to push on new HTML 
<meta> conveniences for specifying a custom loader to use for the remainder of 
the program. In that context you could create a loader with a nice, clean 
global scope.

# But then what about static scoping?

Compile-time checking of variables is a really important goal, and we don't 
have to abandon it. We just do the checking only within module code, relative 
to the current contents of the global object. This means it's still technically 
possible for globals to disappear at runtime (and consequently throw errors).

But this doesn't bother me. First of all, variable errors due to dynamic 
deletion of globals are not the common case. Second, you can avoid globals 
entirely by strictly importing from modules. Finally, you can easily create 
loaders with frozen global objects. An environment like SES might choose to do 
this. But we retain compatibility in the default browser context.

If you ask me, this compromise is **totally** worth it.

# Anonymous modules as a simple way of opting in

A common pattern on the web is to protect yourself against creating unnecessary 
globals is to wrap top-level code in an IIFE:

    (function() {
        /* program goes here */
    })();

By allowing anonymous modules, we simultaneously make this more idiomatic and 
implicitly opt in to all the goodies of ES6:

    module {
        /* program goes here */
    }

Kills two birds with one stone!

# Avoiding rightward drift

Still, whether you use an IIFE or an anonymous module, the extra rightward 
drift (requiring an initial level of indentation around the entire program 
body) is annoying. A simple pragma takes care of that:

    // equivalent to wrapping program in module { ... }
    use module;

This is not strictly necessary but avoiding an extra level indentation in every 
single program is a big win when you multiply it over every program ever 
written, and some editors will fight you if you try not to indent within 
curlies. But notice that even though this is a kind of opt-in pragma, it still 
is strictly *less* code than you have to write today with an IIFE -- kills two 
birds with a smaller stone than ES5 requires to kill one.

# Summing up

We can make ES6 100% backwards-compatible, using modules to opt in to 
backwards-incompatible changes. It replaces the notion of a new language "mode" 
with the simple syntactic characteristic of whether the code is in a module. 
And you can now mix old code and new code freely, even within the same <script> 
tag or source file.

One final note: the "...;version=6" MIME-type could be used to hide ES6 scripts 
from downrev browsers. So it may still have a place, but not for opting in to 
new semantics.

Dave

[1] https://plus.google.com/112284435661490019880/posts/6W7ErmRC1XN

[2] We can't be future-compatible, as some have hoped to accomplish with HTML, 
except maybe with macros. We'll get there if/when we can.

[3] http://wiki.ecmascript.org/doku.php?id=harmony:egal#is_and_isnt_operators

_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to