When in Rome, do as the Romans do.

If you're programming in a dynamic language, there are things that you do that would not easily fly in a static language. Things like tests for truthiness. On first blush, from a static background, something like "var p = x && x.y && x.y.z || w;" look terrible, but they're pretty standard ways of cascading through different options without causing null pointer exceptions in a language like JS. When you start introducing type constraints, you are committing yourself to getting rid of all those dynamic shortcuts.

Yes, shortcuts can sometimes get you in trouble, but they can sometimes get you down the road so fast that trouble doesn't have a chance to find you. That's sort of the tradeoff you make: we can be super-fast 90% of the time, at the cost of having difficult to debug problems occasionally.

On the other hand, when you have a requirement to have type constraints in your code, any other code you interact with has to also have type constraints. But in JS, that's not a lot of projects. There is the DefinitelyTyped repository, but it's incomplete and of unknown quality.

I had thought the same thing as you, "beach-heads of type safety". The problem is that dynamic code tends to infect static code. I eventually gave up on TypeScript after not being able to wrangle the combination of a few code generation and graphics tools. My project was already reasonably OO organized and not dynamic, but there was just no easy way to handle the boundaries of the APIs. To deal with it, I had to... start using the dynamic features of JS, giving up on the type safety.

For example, you can't do function overloading in JavaScript, because you don't have any information about types on which to differentiate functions. Well, it turns out that means you also can't do function overloading in TypeScript. The only reason Math.min works in TypeScript is because JS only has one number type, and it is 64-bit IEEE floats.

But some libraries in JS, and especially in DOM, do overload functions. You test the type of parameters at runtime and decide what each positioned parameter actually means. So if you want to use such a library in TypeScript, you need to have a type definition that uses Any for each of the parameters, meaning you've lost type safety and you're back to testing the type of things.

So it just leaves you in this limbo zone where you don't get to use the features of the native language that make up for it being so crappy, nor do you ever get to use the features of the transpiled language that promises to keep your code easy to modify over time. So that's what I mean about "worst of both worlds".

It's like trying to introduce rules on a group of anarchists: you're more likely to get burned at the stake than to make more productive anarchists. Or try letting a bunch of corporate lifers work without a direct boss: you're more likely to end up with a lot more donuts eaten than code written. What ES6 gets right is that it doesn't try to bolt on a type system into an ecosystem that just won't tolerate it. It has features for making it easier, less error-prone, more ergonomic to do things that people are already doing in ES5. In particular, people are already trying to make classical classes with inheritance out of the prototype-based class functions in ES5, so ES6 introduced a new syntax for just that use case that gets rid of all the repetitive and goofy "Object.create" and "MyClass.prototype.myMethod = function(){ blah blah blah blah }" stuff.