For those considering writing code in the latest version of ECMAScript and transpiling it to ES5, I have some advice for you: Don’t. ES5 is far from perfect, but don’t expect “the future” to have fewer problems than the present.
The definition of transcompilation ought to deter would-be practitioners:
A source-to-source compiler, transcompiler or transpiler … translates between programming languages that operate at approximately the same level of abstraction …
What is the point of compiling code at “approximately the same level of abstraction?” It sounds like we accomplish approximately nothing. According to Alan Kay, if we want to build faster and grander, then we actually need to raise the level of abstraction by orders of magnitude.
Is compiled ECMAScript, overall a set of subtly different syntaxes for existing language features, really worth the attention of language designers and compiler authors?
I agree with Douglas Crockford’s assessment that a language should drive us towards writing error-free programs. New ECMAScript specifications were surely well-intended, but they bring little in terms of bug avoidance. Several new features probably threaten correctness:
- Arrow functions’
thiscontext and syntax can cause confusion.
classencourages use of problematic features like the
- Even well-planned class hierarchies tend to break down, making
superlikely sources of poor architecture.
- It’s possible to load classic scripts via
import, but they are treated as module code, which has subtly different semantics and might cause unexpected behavior.
When I have the choice, I’ll just ignore the new problem-causing bits, and only use the parts of the language that I like. But since the above features were intended to be and are popularly believed to be beneficial, it may be difficult to keep the camel out of the tent (once his nose is in).
Suppose that you have the willpower to reject troublesome features in the language. At that point, the only new features that seem wholly good to me are:
- Object literal syntax
- Default, rest, spread parameters
- Tail calls
I’ve left out Promises and the new miscellaneous built-in methods, because those are already available via libraries. Transpilers don’t fully support tail calls, so all I have to gain at this point are ways to make my code look fancier. There wouldn’t be a problem with that, all else being equal; but all else isn’t equal, and there isn’t enough good with that either.
Transcompilation requires more dependencies and configuration. Parsing code becomes increasingly slow as a project grows, eventually impacting iterative development processes. “Optimizations” to reduce parsing require additional time and code to set up.
Support for source maps seems pretty good, but for my company, it still isn’t perfect. There may be one line of configuration that will prevent Safari from reporting compiled line numbers for errors, or some just-merged bugfix for the compiled line numbers in the “expanded” stack traces of Chrome’s
Promise. But, of course, we wouldn’t need to search for these solutions in the first place if we hadn’t transpiled.
I’ve also found that some new syntax does not map directly to what Chrome’s step debugger considers “lines.” It’s mostly possible to debug the original source code, but the compiled code will still be the most faithful representation of the program state.
One time, I managed to shadow a function parameter with a
const declaration. That’s a
SyntaxError by the specification, but Babel allowed it. The code compiled to
var, which silently overwrote a parameter with the same name. It seems transpilers may cancel or even reverse the benefits of runtime features by creating false expectations for program behavior.
Getting source maps to work in our test environment was quite difficult. We actually went several months without correct line numbers for errors. When we finally sat down to fix the issue, we lost several additional hours before we got it right. I haven’t had the patience to dig into this yet, but I once attempted to debug transpiled Mocha tests with node-inspector. 10 minutes later, the tests started running.
We spend a disproportionate amount of time fixing bugs. So how could we even contemplate choosing a technology which could make the hunt take longer? Can you really tell yourself that some bag of syntax tricks is a fair trade for your time?
Even though I do like some parts of the new language, I can’t justify transpiling it. Instead, I choose ES5; for a simpler, instant build (i.e., no build) during development; and to utilize existing tools without friction; and rather than enhancing my code with new syntax, I’ll structure it around userland libraries, whose offerings exceed what the standards bodies provide; and finally, I’ll get back to “real work.”