Blog Contribute

Compiling OCaml to JavaScript

November 9th, 2017 - Sander Spies

Code on the internet is often a rather static affair, you read it and when you want to do more complex things, like writing code, you use an editor on your local machine. There are websites where you can also edit code online, but these often require a server because the supported programming language can't be compiled to the web. This means that these online editors are not very portable to other websites.

OCaml has the advantage of being able to run code on the web without needing a server. ocaml-gist uses this advantage to create online gist experiences that run completely standalone. It allows library authors to give users a place where they can try libraries immediately. It allows for embedding in any form of web page, like for instance this blog post:

(* change me *) let rec fib n = if n = 0 then 0 else if n = 1 then 1 else fib (n - 1) + fib (n - 2) let example = fib 20

This is the first blog post in a series about ocaml-gist. In this blog post we dive into the technology used by ocaml-gist that allows OCaml to compile efficiently to JavaScript.

OCaml is a great fit to compile towards JavaScript. Thanks to a sound type system it's possible to guarantee that a whole set of errors won't occur at runtime, this makes it possible to compile OCaml code very efficiently. Also, the OCaml compiler is very capable at optimizing code and removing unused code. Note that by default you won't need to do anything for this, it all works out of the box.

ocaml-gist consists of two parts, a webworker and a frontend. The webworker evaluates OCaml in a separate thread and ensures that the frontend only needs to focus on the interface and remains responsive.

Webworker

The webworker is created through Js_of_ocaml, which is a library that takes the bytecode produced by the ocamlc compiler and turns it into JavaScript. OCaml bytecode is a binary format that contains operations for the ocamlrun interpreter, which executes the bytecode. ocamlrun is a stack machine which means that the operations are executed in order. For example if an ADDINT operation is performed it will pull the the two latest values from the stack and push a new value on the stack. Js_of_ocaml is similar to ocamlrun as it also runs the code through a stack machine, but instead of executing the code it produces an intermediate format that is easier to translate to JavaScript. In this format in turn optimizations, when enabled, are performed like: inlining functions, turning tail calls into trampolines, and deadcode elimination. As a result very efficient JavaScript can be created.

An advantage of using bytecode as input is that Js_of_ocaml can easily support newer versions of OCaml as the bytecode format does not tend to change a lot. A disadvantage however is that the produced JavaScript is not really great for debugging, which means that integrating it in existing JavaScript environments can be troublesome.

Another feature that ocaml-gist uses from Js_of_ocaml is the ability to create toplevels. Js_of_ocaml creates toplevels by compiling the OCaml toplevel's toploop to JavaScript through Js_of_ocaml. This makes it possible to evaluate OCaml code in the browser without needing a server. To make this rather trivial Js_of_ocaml has the jsoo_mktop command which ocaml-gist also uses.

Frontend

The frontend of ocaml-gist uses BuckleScript instead of Js_of_ocaml. BuckleScript is a fork of the OCaml compiler that has more focus on producing readable JavaScript compared to Js_of_ocaml. Instead of using bytecode, BuckleScript uses the intermediate lambda format of OCaml and turns it into JavaScript. This allows for a greater level of control on the output and helps with producing code that can be read by JavaScript developers. As the ocaml-gist frontend needs to be able to integrate within JavaScript environments this is a great fit. Forking the OCaml compiler comes with its own set of problems though. Most importantly being out-of-sync with the OCaml compiler.

As we aim for readability by JavaScript developers, we've also chosen to use the Reason syntax instead of the default OCaml one. Reason is a syntax extension on top of OCaml that aims for readability by JavaScript developers. It's fully compatible with the default OCaml syntax and doesn't go beyond using the existing OCaml AST. It also provides great support for React.js, the frontend JavaScript library that is used by ocaml-gist, through the ReasonReact bindings and JSX support.

As ocaml-gist itself shows, OCaml is a great fit to compile towards JavaScript. It provides features like a sound type system and deadcode elimination that allow it to compile efficiently to JavaScript, and Js_of_ocaml and BuckleScript help with compiling to JavaScript for different audiences.

In the next blog post about ocaml-gist we'll be diving into the type based feedback support provided by Merlin-lite.