Transpiled For-Of Loops Are Bad for the Client
daverupert.com
daverupert.com
The Babel homepage literally says what it does.
> These plugins allow you to use new syntax, right now without waiting for browser support.
When a Twitter user tried to help him by asking how a syntax transform could polyfill `forEach` on a `NodeList` -- something that would be impossible in JS -- he was completely dismissive.
> Science? ️ I dunno. Maybe if the object was previously queryselected? [...] I'm way past that problem now.
Polyfills can be heavy because they have to be forward compatible and implement behavior to spec. If they don't then you end up shipping code that breaks when browsers are updates and the polyfill becomes redundant. That's how you break the web.
And added this option + optimization for v7 https://github.com/babel/babel/tree/master/packages/babel-pl... so can look forward to that now =D.
And a few months ago that we considered moving to something else, Coffee Script 2 was released with all that was missing for us.
Sometimes its fine.
Other times it's madness or pulls in a massive polyfill. That's sort of inherent to making a transpiler: they need to match the spec. I usually just need one specific behavior that could be written in a 3 line helper function.
In loose mode they might not follow the letter of the spec, but can cheat for speed or size.
Only thing I can think of (since changing the default to be non-spec would be odd) is just warning on larger code size outputs and saying you could try loose mode in certain plugins/situations. Lastly would be do start creating a tool that does runtime checks to determine how small the compiled output should be
It's one thing to wait for the actually supported browsers to all catch up on a new feature, but IE11 is abandonware that can't be abandoned, and the situation just keeps getting worse.
[].forEach.call(myNodeList, myCallback)edit: now reading about for..await..of. Interesting, I never used that.
Sure you need a little more setup, but it's not only better for clients, but it's also much better for development because your source maps, breakpoints, and actual variables are much closer to what the browser is actually executing (this is especially important for generators because they compile to case statements that aren't easy to debug).
Several years ago, I decided to try out the then-newish E4X for-each syntax. It was only implemented in SpiderMonkey, but that was okay because not only am I not a web developer, this project wasn't meant to run on the web, either—it just happened to be in JS. I knew for a fact that this code wouldn't be running anywhere except SpiderMonkey, so I dug in. After some research, though, I discovered some problem. Some subtlety that made it a non-starter. So I kept writing traditional for loops.
A couple years later, I was starting work on a kind-of-new codebase; it was essentially greenfield, but I knew I was going to end up cribbing a bunch from an existing codebase. When my fingers began typing out the familiar `for (`, I paused. This would be the first for loop introduced in the project, and I should maybe make a decision to get things right from the beginning. By now, for-of had arrived, and it was being standardized in ESx proper. Should I maybe do another survey of the lands and look into giving the new iteration syntax a shot? It was a simple iteration: a straight pass through a list of items, one after another. And in addition to being able to address items by index, they all had the equivalent of a "next" pointer for the item that followed. So I deferred; I went with a while loop instead, chasing "next" pointers to avoid having to do some in-depth research on the then-current state of `for`. I got pretty far doing this, actually, and was able to go the next few days writing all my loops in this way, just to avoid tacitly endorsing either for-of or traditional for loops.
After several days, however, that ran out, and I came to point where I would not be able to sidestep it and needed to make a decision. So I stopped, and dug in again. Another problem surfaced: for-of was only implemented in the newest releases of SpiderMonkey, and the only enhanced iteration syntax available in previous releases that this code would need to run on was the now-deprecated for-each. I thought really hard about this. Was there some part of the language that could be exploited to find some clever way around this? No. Maybe consider a transpiler? After all, for-of would be standard, so eventually support is going to be widespread enough that even the lowest common denominator will have native support...
But then I stopped. Because the whole thing is stupid. Just write a damn for loop. What was the real problem? Trying to save time? Except how much time was already sunk into trying to shave off, what? Like one second of typing? Maybe two? And the tradeoff for that development time gain is (a) a runtime performance penalty; (b) having to either throw everything older than version x by the wayside or spend all those extra seconds and more to put some transpiler crud in place; and (c) code that's harder to understand for anyone who doesn't fetishize staying on top of the newest language features. The latter also involves people who typically work in other languages and would probably immediately understand `i = 0; i < n; i++` (even if it's not idiomatic for their preferred ecosystem), but likely wouldn't immediately grasp the nuances of the new iteration protocol.
What's wrong with `i = 0; i < n; i++`, anyway? It's not even that verbose. What it is, is predictable. Which means your eyes get used to it, just like any other pattern. Except traditional for loops are powerful, too, because you might want to do something, say, for every fourth item. Or maybe skip the first. Or iterate backwards. And you can. In each case, anything not fitting the traditional pattern will stick out.
Anyway, I still write traditional for loops today. I don't feel like I'm missing out on anything, and my thoughts very much echo the sentiments in an essay published a few years ago on a similar topic: "Java for Everything".
https://www.teamten.com/lawrence/writings/java-for-everythin...
For loops are harder to skim.
Still, I don't understand the for loop hate. I have seen people argue against 'pointless optimizations', in the name of readability. And while yeah, arr.filter().map() is probably faster to comprehend, you're still iterating through the array twice. Pointless optimizations, not so much when the array is thousands of elements long.
The wonders of modern processors and compilers.
Getting the data aligned in a cache friendly way returns those loops to the expected behavior of one iteration being faster than two.
How did Microsoft manage to realize the necessity of coroutined iterables in a threaded, impure, multiprocess-capable language like C# (without breaking backwards compatibility, might I add) with Linq, and yet JavaScript couldn’t?
(If I may be permitted to go slightly off topic here, I also think it’s a damn shame Rust devs were so eager to release v1.0 of their shiny, new language to attract the masses that Rust - the language, not stdlib - shipped without native concepts to accommodate iterables and async/await. Unthinkable this side of 2010 for a language that aimed to be the second coming of C++).
https://blog.mariusschulz.com/2017/06/30/typescript-2-3-down...
From the article:
"TypeScript 2.3 introduced a new `--downlevelIteration` flag that adds full support for the ES2015 iteration protocol for ES3 and ES5 targets. `for...of`-loops can now be downlevel-compiled with correct semantics."
I just tested it, and the minified and compressed babel-polyfill is 34K. Going from 12K of JS to 46K of JS just to make modern loops work may be a little unsettling, but those are both really small numbers these days. The blog post is about 10x bigger. It's also worth noting that babel-polyfill adds lots of other useful stuff; there are plenty of similar problems that you'll never have to think about if you're willing to drop in the whole babel-polyfill.
I think that for most use cases, bundle size is a distraction and it's best to just focus on building things. You should still keep an eye on bundle size in case it gets out of hand, but I don't think that's the case here.
All that said: dropping all the fancy logic and just going with plain JS has some nice simplicity to it. In the JS world you can spend a lot of time on tooling.
At the end of the day you ask yourself "do I really need to add 34kb for 'for..of' or can I get away with a small concession?" like the blog post asked. You may find that it might not cost you much to pay attention to your trade-offs.
Whining about the former is to just whine about history, like how 'referer' is misspelled.
Epiphany of the year there.
The overarching problem is that the web platform itself is so fragmented, with different levels of support for various features, that polyfilling and transpilation are important. Strictly speaking, if every relevant browser supported for..of natively, there should be no need to transpile.
You could avoid the whole for..of problem by writing ES5 JS code, but for many people that's apparently a worse option than dealing with Babel.
Yes, and I don't understand that at all. I don't have any problems writing plain old ES5 code.
TypeScript has been a revolution in the way that I write client side code, been able to write in a superset of a language I already know against existing code in a way that I and my tools can reason about and with faith (backed up by experience) that I can write it once and know it will work basically everywhere is huge.
Is it a perfect tool? by no means, it's output messages can be a bit opaque.
That I can also use features now that won't make it into browsers everywhere for a while and that they'll gradually replace those features as browser support for them increases, ES6 was a good step in the right direction (and you can target it from TS and in many cases there aren't that many differences between clean ES6 and TypeScript's output but I can also set the target to ES5 and use it).
FWIW: https://github.com/Microsoft/TypeScript/pull/12346
https://basarat.gitbooks.io/typescript/content/docs/types/li...
The big reason I'm so excited about web assembly is that now we can escape the albatross of JS/ES. I've started playing with rust targeted to emscripten and it's such a breath of fresh air (both the language and toolchain).
Statically compiling a simple hello world C program results in a 700+ KB executable. By researching And adding various options to gcc to reduce size, I was able to get the binary down to just under 650 KB (277 KB gzipped), so progress! :/
There are undoubtedly smaller libc implementations[1], but I think I've illustrated the problem adequately. Having an entire runtime in the browser gives JS a fairly large advantage as I see it, at least currently.
1) Generics are instantiated and compiled separately with types for which the generics have identical behavior.
2) Functional abstraction is encouraged (not a bad thing!) and inlining heuristics are overly aggressive to compensate.
3) By default, almost every function call has to deal with the possibility that the callee might throw an exception. This is smaller than 1) and 2), but it still matters.
https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
Idiomatic Rust doesn't use them for error handling, and they can be disabled entirely with a command-line option, but the implementation still has to deal with all of the issues that come with having exceptions.
True. However from my POV the code bloat is outweighed by the fact that rust tooling is simply that good. Javascript is, IMO, a nightmare to develop for both due to poor language choices and due to the really mediocre tooling.
My new favorite JSism (thanks HN!) was this:
['1','2', '3'].map(parseInt)
Unfortunately, both Netscape and MS wanted control, and then it was too late.
Now we can have this, too, of course, and e.g. run Clojure or Elm in a browser, compiled down to a safer / better supported subset of JS, or use ES7 with polyfills. But the intention of browser vendors is currently different; WebAssembly, while a small and well-defined VM, is not intended as a JS peer, let alone replacement, though.
I would rather claim that all common VM targets, such as LLVM, CLR, JVM etc. are so full of semantic subtleties that the subtile parts of JavaScript are quite harmless against them.