I have successfully used Haxe in isolated cases to build games for WebGL and Canvas. I think transpilers are most useful when you rarely want to reach outside of your own code, or you only need to rely on a few key libraries that are known to be well supported within the ecosystem of your transpiler language. I also struggle without a static compiler, so for my case it makes sense to use a transpiler, even with all of its pitfalls.
If there is one thing however that I have learned from using transpilers it is that externs as a whole are a huge waste of time, as it can be difficult to determine their quality upfront. If you are building a Node backend or SPA, you are going to need to rely heavily on existing libraries and will probably find yourself constantly hitting walls while you try to swim against the current of the ecosystem if you try to use a transpiler.
I disagree about externs being a waste of time for Haxe. I've written several, and they've been instrumental in helping me understand a codebase, and manage an upgrade process.
Haxe has a lot of metadata mechanisms that enable mapping language-specific semantics to Haxe standards (@:selfCall, and @:native for example). Haxe also has abstract types, which enable automatic inline runtime conversions (e.g., converting a native type to a standard Haxe type automatically depending on usage in haxe methods).
It's true you can't expect to find a high quality extern for a given version of any given library. However, it's pretty easy to write a small one for the subset of functionality that you require. It always ends up paying off for me.
I should have elaborated a bit more on this point, and I mistakenly used the wrong terminology. I was thinking specifically of Typescript type definitions when I wrote about externs, and was meaning to refer more so to the concept in a general sense (across all transpilers). That said I do use a number of high quality externs with Haxe (the Pixi.JS one is excellent).
I agree with what you say about writing your own externs with a subset of the functionality, and I think it is often a better approach than relying on potentially flakey externs. Still I think this sort of workflow is only suited to particular types of projects and especially in a team environment it makes more sense to avoid niche ecosystems.
I don't have an opinion on this particular compiler, or really any other similar one, but: Can't one make the same argument about a compiler from C++ to x86 machine code? C++ and x86 certainly don't have anything like a 1:1 mapping, but people use those all the time.
I think an important question is whether the compiler output will need to be touched by humans. If it's just going to be run or compiled more or whatever then it doesn't really matter how the various features map.
As someone working on a compiler that turns a C# library into Java and JavaScript ... for us a significant benefit is that we can share code between our different products. We write the code in C# and about 95 % of the library code is the same for every platform, so implementing a feature for one platform automatically benefits all of them (which have been at one point: Java Swing, JavaFX, HTML5/JavaScript, .NET/WPF, .NET/WinForms, .NET/Silverlight, and Flash). In very many cases all that's needed is a simple merge and converting the code again.
Yes, languages differ and language features rarely map 1:1 onto each other; that's about the other 5 % (as well as platform differences). But a lot of things can be done automatically if you control the conversion process, e.g. converting C#'s extension methods into default methods in Java. Of course, if you don't require the resulting converted code to be worked with, and just need it to run, then you can do a lot of shortcuts. We translate at the source code level because at least the Java code is still maintained and extended on the Java side. Doing it at the IL level would be simpler in some cases, but is impractical for a lot of others.
> If behavior of compiled program matches the original one, that is.
Unfortunately, that does make it difficult when your target is JavaScript. Every mature transpiler I've used has had some semantic gotchas that aren't present in the "proper" compiler.
Routines can be created to mimic desired behavior. Sprinkle in some conventions and limitations, and you have a useful tool for a large set of use-cases.
Maybe its my own ignorance, but language design often doesn't map 1:1, so wouldn't it end up feeling forced?