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