Skip to content

Instantly share code, notes, and snippets.

@deathcap
Last active February 17, 2016 07:32
Show Gist options
  • Save deathcap/89975d8ede56f27d83e7 to your computer and use it in GitHub Desktop.
Save deathcap/89975d8ede56f27d83e7 to your computer and use it in GitHub Desktop.
My Big List of Annoyances with Babel/ES6 Transpilation

Complicates development-code distribution: When a user checks out a new revision of the source code from git, the changes won't immediately have any effect, unlike (most) changes from non-transpiled modules. Instead they have to be sure to run npm run-script prepublish to recompile.

Actual user conversation from chatroom (lightly edited for relevance):

developer Jan 24 15:33 update, I fixed that recently
user Jan 24 15:33 ooooohhh, ummm
  the current [module] has that update, or do I need a special fork version from you?
other-developer Jan 24 15:35 It was merged yeah
developer Jan 24 15:36
 make sure to npm run-script prepublish after updating if you checked out from git
confused-user Jan 24 15:37
 I am not sure what republish or checked out from git means
 I know I set up a git repo for my stuff at one point
 bt I've been going to the git page, download as zip, and then copy and replace into the folder I already have to replace my old version...

Normally, you only have to run npm install when updating if it contains new/different dependencies. Transpiling makes this step mandatory.

Possible ideas to improve the situation:

  • Checkin the build process output? (has disadvantages - intermediate products in source control)
  • Document clearly how you should always npm install after checkout? (annoying extra step)
  • Refer user to download the zip release archives instead? (then diverges from developer workflow of git checkout)

Toolchain development complication increase: Adds an extra step to making code changes during development. With non-transpiled code you can make changes to your module and go. Babel requires extra processing before the code is usable.

This can be implemented in various ways. Gulp (or grunt) is a common tool to run babel, introducing extra complexity to the build process. Another tool is browserify, which can run babel through the babelify transform.

Possible ideas to improve the situation:

  • Learn the tools (but it is more to know and master, cost has to be compared versus benefit)
  • Use gulp watch (e.g.) - automatically watches and rebuilds, but see below

Silent failure mode with rebuild-watching tools: When you use gulp watch or similar, it uses fsevents to conveniently rebuild your source code as it changes on disk - but, this means you have to a) know to run this command and keep it running, and worse b) watch it (ironically) for error output.

If you make a change to the transpile input file and save, then try to test your app, it might run just fine — except, nothing changed in the transpiled output! Because there was a compile error, the new version was not written.

Possible ideas to improve the situation:

  • Always continuously watch the transpile status, maybe develop a notification tool to better alert failures, if there isn't one (but then you have to install this tool and watch it)
  • Change the tool to write the transpile error into the output? (actually browserify transforms seem to do this, which is nice)

Rebuild time lag widens feedback loop: reducing the time between input and output improves efficiency, but babel at least through gulp in this configuration adds a 2 - 3 second lag between iterations:

> gulp

[21:53:52] Using gulpfile gulpfile.js
[21:53:52] Starting 'compile'...
[21:53:52] Finished 'compile' after 689 ms
[21:53:52] Starting 'default'...
[21:53:52] Finished 'default' after 20 μs

real	0m2.786s
user	0m2.825s
sys	0m0.240s

Possible ideas to improve the situation:

  • optimize babel to run faster (but at what cost?)

Source code in development ≠ compiled code in development: the answer is "source maps", but it has issues:

Source maps unhelpfully show up in grep results: I often want to search the source code for some word, using the familiar Unix grep tool at root of the project (or better yet, ack or ag) - but the source map build process adds its own .map files, which necessarily contain the same words as in the actual source code I am searching for. This causes superfluous hits in the search result, which are invariably on long lines and full up several pages of the command output:

ack client
[... some good hits in sourcefile.js...]
dist/maps/sourcefile.js.map
1:{"version":3,"sources":["sourcefile.js"],"names":[],"mappings":";;AAAA,IAAM,OAAO,QAAQ,SAAR,CAAP;AACN,IAAM,QAAQ,QAAQ,UAAR,CAAR;AACN,IAAM,SAAS,QAAQ,WAAR,CAAT;AACN,IAAM,SAAS,QAAQ,QAAR,CAAT;AACN,IAAM,iBAAiB,QAAQ,gBAAR,CAAjB;;AAEN,OAAO,OAAP,GAAiB,UAAS,MAAT,EAAiB,OAAjB,EAA0B;AACzC,SAAO,YAAP,GAAsB,IAAtB;AADyC,OAEzC,CAAM,SAAN,EAAgB,QAAQ,IAAR,CAAhB,CAFyC;AAGzC,MAAM,cAAc,EAAC,MAAM,QAAQ,IAAR,EAAc,MAAM,QAAQ,IAAR,EAAzC;;AAHmC,MAKzC,CAAK,WAAL,EAAkB,UAAS,GAAT,EAAc,QAAd,EAAwB;AACxC,QAAI,GAAJ,EAAS,MAAM,GAAN,CAAT;AADwC,SAExC,CAAM,eAAN,EAAsB,QAAtB;;AAFwC,QAIlC,OAAO,SAAS,WAAT,CAJ2B;AAKxC,UAAM,qBAAN,EAA4B,IAA5B;;;;AALwC,QASlC,0BAA0B,SAAS,OAAT,CAAiB,IAAjB;AATQ,QAUlC,kBAAkB,SAAS,OAAT,CAAiB,QAAjB;;AAVgB,SAYxC,sBAAyB,2CAAsC,eAA/D,EAZwC;;AAcxC,QAAI,4BAAJ,CAdwC;AAexC,QAAI,wBAAwB,OAAxB,CAAgC,GAAhC,MAAyC,CAAC,CAAD,EAAI;;AAE/C,yBAAmB,wBAAwB,KAAxB,CAA8B,GAA9B,EAAmC,CAAnC,CAAnB,CAF+C;KAAjD,MAGO;AACL,yBAAmB,uBAAnB,CADK;KAHP;;AAOA,QAAM,cAAc,eAAe,0BAAf,CAA0C,gBAA1C,CAAd,CAtBkC;AAuBxC,QAAI,CAAC,WAAD,EAAc,MAAM,IAAI,KAAJ,4CAAmD,2CAAnD,CAAN,CAAlB;;AAEA,YAAQ,OAAR,GAAkB,gBAAlB,CAzBwC;AA0BxC,YAAQ,eAAR,GAA0B,eAA1B;;;AA1BwC,UA6BxC,CAAO,OAAP,GAAiB,gBAAjB,CA7BwC;AA8BxC,WAAO,KAAP,GAAe,OAAO,WAAP;;;AA9ByB,QAiCpC,OAAO,gBAAP,EAAyB;AAC3B,aAAO,gBAAP,CAAwB,OAAxB,CAAgC,UAAC,IAAD,EAAU;AACxC,aAAK,QAAL,EAAe,MAAf,EAAuB,OAAvB,EADwC;OAAV,CAAhC,CAD2B;KAA7B;;;AAjCwC,UAwCxC,CAAO,IAAP,CAAY,iBAAZ,EAxCwC;GAAxB,CAAlB,CALyC;AA+CzC,SAAO,MAAP,CA/CyC;CAA1B","file":"sourcefile.js","sourcesContent":[... the word I was searching for].. etc..

Possible ideas to improve the situation:

  • In practice, I end up first searching in the root (.), then remembering this is a transpiled project so I need to narrow the search to src/ (but having to do this separate thing for this project adds an extra mental context switch)
  • Configure the search tools to exclude the dist/ directory and/or .maps files (probably a good idea, but varies per tool)

Source map interaction in transpiled modules versus browserify-bundle source maps: haven't fully investigated this, it may not even be relevant any longer, haven't checked, but it seems the bundled .map files in transpiled modules interact poorly with browserify's bundle source maps:

deathcap/wsmc#16 Source maps sometimes not loading in debugger

The babel-originated sourcemaps are/were stored in a separate .js.map files using //# sourceMappingURL=maps/sourcefile.js.map, which might conflict with browserify. There seems to be reliability issues in general with source maps, see also: PrismarineJS/node-minecraft-protocol#350.

Possible ideas to improve the situation:

  • Investigate the bugs and work to resolve them (if its worth it)

Are the tests transpiled, too? Requires a decision to transpile or not transpile the unit test and sample code. If so, then they have to be invoked with babel-node instead, a discrepancy to remember with running non-transpiled scripts; if not, then node (or other test tool runner like mocha), but tests/scripts are then confusingly in a different dialect than the main module code.

Possible ideas to improve the situation:

  • Move tests to a separate module and add its own babel/gulp system? (but then how to best keep it all in sync? seems worse)

Stack traces in tests/scripts do not match up with actual source code lines, if the main module is transpiled but the tests/scripts are not.

Possible ideas to improve the situation:

  • Transpile the example scripts and unit tests too? (but see above)

Potential divergance from standardized JavaScript: as babel is implementing new ES6/7/etc. features, under various stages of development, there is the potential for their behavior to differ at this point in time to how they end up being finalized in the official standard, if at all.

Example of a real but subtle behavioral change I've noticed:

$ echo 'const a = 0; a = 1; ' | babel
SyntaxError: unknown: Line 1: "a" is read-only
> 1 | const a = 0; a = 1; 
    |              ^
  2 | 

is the result on babel@5, a result I've found useful because the compiler detects the constant reassignment at compile-time, helpfully catching it before it gets to runtime. I've written code (mainly converting from ES5 var) assuming const will be compile-time detected, and if so then I can change it to let. This is desirable behavior from my perspective, a reason to use babel.

... but it changed in babel@6, no longer is const reassignment a compile-time error:

$ echo 'const a = 0; a = 1; ' | ./node_modules/.bin/babel
const a = 0;a = 1;

Why not? idk. If anyone knows I'd be curious for the answer.

Of course now that ECMAScript2015/ES6 is out, it is not too much of a concern anymore, but changes could happen in projects adopting future language features (ES7 and beyond). If the project is developed today depending on non-standard language features, then if the features are standardized with different behavior, the project would need to be updated to use the new standard or stay on an obsolete language dialect (pidgin?).

Possible ideas to improve the situation:

  • Only rely on standardized features (ES6 at the time of this writing), do not enable "stage 0" (etc.) proposals

Not taking advantage of native ES6 features: As browsers and other JS environments begin to implement ES6 natively (and they have been making great progress, many/most important ES6 features are in production Chrome and Node.js, see: https://nodejs.org/en/docs/es6/ and https://kangax.github.io/compat-table/es6/), transpiling ES6 down to ES5 becomes an increasingly archaic strategy. Granted, if you need legacy ES5 (or ES4) runtime/browser support, by all means.

But is it worthwhile to compile the latest-and-greatest to the lowest-common-denominator, or more prudent to concede to the decently-well-supported? Chrome 48 (released last month and it auto-updates so nearly all Chrome users have it in production) and Node 4 (the current LTS/long-term-support release) both support many fundamental everyday ES6 constructs including const and let in strict mode and classes.

Possible ideas to improve the situation:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment