Having a single language which is capable of writing code for backend, frontend, and mobile can simplify development, improve communication between user advocates and the developer, improve internal team cohesion, make on-boarding new developers easier, and improve long term maintenance costs. I know because we do this at my company using Ruby.
To show the power of this approach you might want to watch this short video: https://www.youtube.com/watch?v=GEe7hHIhyUs. The essential idea is that by sharing classes (with possibly different internal implementations) between client and server the client can access server objects transparently, essentially allowing the program to develop code as if on a single platform.
Each of these choices has negatives, which Crystal, if it could also run in the browser would I believe solve.
Ruby is the closest to perfect, but because of Ruby’s runtime complexity the resulting code size when transpiled to JS is large, and the code runs slower than the JS equivalent. Even with this disadvantage it’s a great solution, and we use full stack ruby (meaning integrated code is running on client and server) on 3 fairly complex applications.
Kotlin is by design intent isomorphic so out of the box it does work well running everywhere, but the language remains verbose. The equivalent full stack application in Kotlin is about 6X the number of lines of code than the Ruby equivalent (using the https://hyperstack.org framework.) Also due to Kotlin’s lack of any real meta-programming capability, it can’t put together decent DSLs which are really needed to reduce the programmer workload.
Clojure, is the least known by myself personally, but have been told by those using it, that it works great as a full stack isomorphic language. However I really don’t see the LISP syntax catching on widely anytime soon.
So what if we took Crystal (or realistically a rich subset) and made a JS transpiler like Opal provides for Ruby?
it would have all the pros demonstrated by Ruby, plus it would have a much smaller faster foot print in the Browser. The added (really a huge) benefit is that the Crystal Macro approach to meta programming / DSL creating, enables even cleaner DSLs to be created.
How to Do It?
Hopefully the justification will get some people interested in moving this conversation forward. The question then becomes how?
I would propose first that the potential power of Crystal-Everywhere ™ is so great, that it is worth at least initially developing a quick, possibly dirty, first implementation. It may not be as fast or as compact as a final solution, but if it gets people interested, there will be additional contributors to help speed things up.
With that in mind lets consider the options:
LLVM -> Emscripten: Long term this might be best, but its hard due to GC issues, and getting the end code to interoperate with JS.
LLVM -> WASM: Long term this might even be better, but in addition to the problems with the Emscripten route, it also has the problems associated with WASM (as in its not really ready for prime time on a number of fronts.)
So the final approach would be the one I would propose taking. Here are the details of how I would go about this.
Collapse Types that have different machine reps, but are semantically the same
In a JS world Ints are all the same, and while strong typing can still work, the resulting code will be the same regardless. Apply the same process to any other types like this that are simply different because of the machine representation.
Compile time errors for unimplementable features
With this approach certain features especially around low level machine access are not going to be possible in JS. Even without these features you still have a beautiful language. And because of macro expansions its easy to control server vs. client side use of these features in isomorphic code.
Map Crystal Objects to JS Objects
With the above caveats (plus perhaps a few others I have missed) a Crystal Object can be directly implemented as a JS Object.
Generate the Code!
I believe with the above rules about how data looks, code gen is pretty straight forward. There might be a few things to work around with closures, and parameter passing, but there are pretty code models that we can lift from the way Ruby/Opal handles this.
I am of course over simplifying, but I just want to get some discussion and interest going on this.