There has been a lot of change in the OCaml -> JS landscape over the last couple of years, and I regularly see questions about what all the names mean. This is my attempt to sort out the world as I know it.

As you navigate the world of compiling OCaml to JS, most if not all of the following will come up at some point:

That’s a lot to remember, but each works in their own similar yet completely distinct way. They can also be mixed and matched as projects see fit. I’m going to use diagrams to attempt to explain all of this and hopefully provide some clarity.

With apologies to those who understand how over-simplified this is, here is a rough overview of how compilers work, specifically in the case of OCaml.

OCaml Compiler.png

The OCaml compiler

Code flows from top to bottom in three distinct phases:

  1. Syntax is read into an Abstract Syntax Tree
  2. Checks are done to confirm the AST has valid semantics in the OCaml language
    • For example, type checking
    • Optimisation is performed at this level too
  3. The AST is written out to a machine executable
    • OCaml supports both native output, like a C compiler, and platform-independent bytecode output like a Java compiler (although not using JVM bytecode, OCaml has its own unique VM).

Are you with me so far? Good. Next we’re looking at the development that happened in 2011 with the creation of js_of_ocaml.

JSOO.png

js_of_ocaml runs after the compiler

This is very simple, on the surface of it. Leave the compiler alone and machine translate the bytecode to JavaScript. This makes JSOO able to handle literally any pure OCaml code, including the diverse ecosystem of libraries, but there are two major downsides:

  • It can be a bit slow, JSOO is effectively a second compiler
  • The resulting JS is mostly unreadable machine code (since that’s more or less what JSOO had to start with). Source maps help here, but they aren’t a silver bullet.

In early 2016, Bloomberg open sourced their answer to this process. Instead of treating the compiler as a black box and working with the result, they dug in and replaced the output phase with Bucklescript.

Bucklescript.png

Bucklescript replaces the compiler backend

This no doubt took a lot of effort to achieve, but the result has some unquestionable benefits:

  • By working with the compiler internals, their output retains the structure of the original code
  • Raw JavaScript types are used (i.e. OCaml strings are JS strings) producing clean readable JavaScript – at least until advanced types are used (here’s the mapping).
  • Most if not all of the compiler speed is retained

Really the only downside I can see is that by working so deep in the compiler they have to chase new compiler releases (as I write this it is still based on the OCaml compiler released in July 2015). Not that this is a particularly bad thing as the OCaml compiler is fairly stable – and it has since been further mitigated as we’ll see in a moment.

A few months after Bloomberg opened their project to the world, Facebook came along and announced Reason. I watched on with great interest as OCaml seemed poised to win the hearts and minds of JavaScript developers.

ReasonML.png

Reason replaces the compiler frontend.

Things are moving a lot faster at this point. Because reason is a 1:1 syntax mapping to OCaml, they’re able to keep up with compiler releases quite easily. This allows them to focus on what they really want to bring to the JavaScript ecosystem; reliable and easy-to-use tooling that leverages the power of OCaml to tempt JS programmers with mostly familiar syntax. And thanks to leaving the rest of the compiler untouched, the same familiar syntax can compile to native binaries that perform better than JS could ever dream of.

But here is where the real fun begins. If you compare the reason diagram to earlier diagrams, you’ll begin to wonder if it could be combined with with either js_of_ocaml or bucklescript. And that’s exactly what happened. When I first looked at this, efforts were made to support both as ways to compile reason source code to JS. That’s still possible, but like all good communities a single recommended approach is appearing and the tools are moving in that direction.

In the last few months the community has settled on the solution that is very fast and produces output JS that can be very similar to the input reason code. Native compilation and tooling still uses the reason compiler, but bucklescript has added first-class support for reason syntax directly into their compiler.

Reason+Bucklescript.png

By their powers combined…

Wow. Check out where we are now.

  • The syntax is familiar to JavaScript developers, and there is a groundswell of effort building to bring react developers in particular on board.
  • The full power of OCaml is available. I won’t sit here and argue it’s the best type system around, I freely admit it has flaws, but OCaml still has tangible benefits over JavaScript. The choice of a battle-tested compiler with a mature and diverse ecosystem is hard to pass up.
  • The output JavaScript is clean, readable, and easy enough to follow that you could check it into source control for team members that don’t want to learn reason yet. I have literally seen recommendations to do this.

Three years ago, I announced my belief that OCaml was the next big thing for JavaScript. Thanks to reason and bucklescript, that future looks to be fast approaching. It’s an exciting time to be a JavaScript developer.

Come join the fun in the reasonml discord!

Advertisements