What is the current state of OCaml-ish in the browser?

My current understanding is:

js_of_ocaml: OCaml → bytecode → js
Reason: OCaml w/ syntax changes → bytecode → js (via js_of_ocaml)
ReScript: new language, similar to OCaml; compiler written in OCaml, does *.res → js directly

In particular we have:

js_of_ocaml, Reason: use OCaml compiler to get OCaml bytecode, convert bytecode to js

ReScript: reason → JS directly (and thus more readable JS)

Does this cover the higher order bits? If I got anything wrong / am missing anything, please let me know. Main interest is running OCaml in the browser, as TypeScript’s type system is a bit frustrating to use.

1 Like

I stopped using ReScript because they didn’t seem to be supporting the original OCaml syntax (which I like) very well if at all.

I haven’t tried it out, but I hear that Melange (GitHub - melange-re/melange: A mixture of tooling combined to produce JavaScript from OCaml & Reason) is a fork of the old Bucklescript project that’s staying closer to OCaml than ReScript. Not sure exactly what that means.

I’ve been using Js_of_ocaml for a few months and it seems very good. After thinking about it a few years I realized I don’t really care about the readability of the generated JavaScript! I never look at it.

2 Likes

ReScript is not just a new compiler written in OCaml. It’s a modified version of the OCaml compiler, which takes a higher-level IR and transforms it into JavaScript. In particular, it has pretty much exactly the same type system as OCaml. The adjustments it makes are all intended to allow a better fit in the JS platform.

One of the most significant innovations of BuckleScript and hence ReScript, in my opinion, is managing to ship a complete toolchain that works out of the box for every major platform including Windows, with no Cygwin/MSYS/WSL/etc. hacks needed.

5 Likes

There is one virtue to producing readable JS that’s often overlooked I feel, probably because it’s hard to empirically measure, but readable JS is more amenable to minifiers, treeshakers, produces smaller and faster artifacts overall. That has been my experience anyway, could just be correlation.

One less debatable virtue is that you can just inspect the JS code to see how you’ll interop with it, or to debug it.

Is it because they rely on the nodejs runtime for their toolchain? How do they do it?

3 Likes

Not by Node.js alone. They went to some trouble to use platform-agnostic tools, like the Ninja build system, npm for packaging, and avoiding any use of platform-specific tools like even make.

1 Like

Anecdotally, I have a card-game-playing app that’s 26,000 lines of OCaml, and the minified JavaScript is much smaller when using Js_of_ocaml than it was with Bucklescript.

The node.js ecosystem is a bit overwhelming, so this may be due to my choosing a poor minifier for the Bucklescript code. On the other hand, i can use the output produced by Js_of_ocaml without minifying it further.

Bucklescript size: 760878
Js_of_ocaml size: 375304

(You can play the game at Master Schnapsen/66)

5 Likes

There seems to be a belief that js_of_ocaml doesn’t do dead code elimination (or “treeshaking”) and I’m not where it comes from.

Js_of_ocaml performs deadcode elimination and minify its out.

Thanks for the feedback.

3 Likes

I would include Melange as a fork of Bucklescript that produces JavaScript from OCaml & Reason. There is a lot of work going on in Dune to support it. Personally I am using JSOO over the other options because I have existing OCaml code and I prefer OCaml syntax over Reason.

Looking at this last year I put together a high-level diagram of how each of the options works. It might be of interest.

2 Likes

Looking at your diagram, I don’t know what you mean by “Javascript code (Bytecode interpreter)”.

Also note that all features/optimizations mentioned in the melange box can also apply to the js_of_ocaml one.

I don’t know what you mean by “Javascript code (Bytecode interpreter)”.

I meant to indicate that the byte code interpreter is running as a JS program, as compared to the regular byte code interpreter which is a native executable.

True the features/optimizations apply to js_of_ocaml, I hadn’t finished my work in detailing that section of the diagram.

1 Like

The code emitted by js_of_ocaml is not a bytecode interpreter. It compiles bytecode into javascript.

For example

let rec fib n = match n  with 0 | 1 -> 1 | n -> fib (n - 1) + fib (n - 2)

gets compiled to

function fib(n)
    {if(1 < n >>> 0){var _a_=fib(n - 2 | 0);return fib(n - 1 | 0) + _a_ | 0}
     return 1}

2 Likes

That’s embarrassing, I’ve incorrectly thought that for some time. Thanks for setting me straight.

Is there technical documentation outside of me reading the code (which I intend to do) that covers the features / optimisations that js_of_ocaml does?
I’ve been compiling a high level presentation on js_of_ocaml to give to an interested technical audience that is used to JS/Typescript or Rust/WASM, and wanted to compare it.

There is old paper from 2011 (https://www.irif.fr/~balat/publications/vouillon_balat-js_of_ocaml.pdf). It’s a bit outdated but gives useful information about the general compilation strategy.

3 Likes

As a part of my PhD, @chambart and I are working on a Wasm backend for OCaml. We presented our prototype and experiments at the WasmGC working group. The slides and the meeting notes are available. At some point we’ll write blogposts/papers on the topic and we’ll announce them on discuss. It’s probably too early to tell more about it.

6 Likes

Interesting, thanks.

Personally I’m always more interested in accessing the browsers APIs (via brr of course) and so far never found myself too limited by js_of_ocaml’s performance.

So my usual question about wasm, what’s the JavaScript FFI story ?

Do you think there would a way to reuse the large amount of extremely boring work I put in brr for binding a non-trivial amount of browsers APIs in an OCaml friendly way ?

For example by subverting the js_of_ocaml FFI primitives ?

1 Like

@dbuenzli : I’m looking at your project ocaml_console (brr.ocaml_console)

I’m curious:

  1. Did you manage to get “enough of OCaml + js_of_ocaml” to run in the browser ?

  2. What is the slow down when compiling “ocaml → js” in the browser ?

  3. How big is this js blob representing “enough of ocaml + js_of_ocaml” ?

We only recently started to think about the FFI related stuff. We’re going to start looking at C code and OCaml FFI inside Wasm. We’re indeed probably going to end up subverting the C stubs. If it works, writings JS stubs to use a library like zarith in the browser won’t be necessary anymore.

I have not looked into the JS/OCaml FFI inside Wasm yet, but I guess the same strategy would work.

In addition to performances, there are others benefits when using Wasm instead of js_of_ocaml, e.g. marshalling of float would work. :slight_smile:

Oh I did nothing. That’s all thanks to the fine work done by the js_of_ocaml developers that make it easy to embed a toplevel in your page, see this bit in the js_of_ocaml manual or this toplevel published by them.

The only twist here was to make the browser extension communicate with the toplevel embedded in your page. That occurs through this poke object that your page must offer; for example simply by linking against brr.poked (see the instructions on this page).

Not sure which slowdown you are mentioning but poking a page is slow to compile because there’s a lot of code to compile. You are basically compiling the OCaml parser, the type checker, the bytecode compiler and the toplevel machinery to JavaScript and js_of_ocaml performs whole program analysis on all that code to remove dead code in order to make your JavaScript as small as possible.

On this poke test, it takes 22s to compile on a 2015 laptop and the resulting js file is 11Mo (OCaml v4.14.0, js_of_ocaml 5.0.1)

Note that I rather consider this console thing as a showoff rather than something practical to work with (personally I don’t use it when I make js_of_ocaml apps and I doubt anyone does, but it was cool to do :–).

Sure but for me there’s no real benefit if there’s no good FFI story with JavaScript to access the browser APIs; otherwise I rather just compile to native code and more than happily scrap the browser :–)

Also what would be the compilation user experience ? js_of_ocaml solved that problem quite neatly by working out of bytecode. I bet we’ll need special opam switches which also compile libraries to wasm artefacts ?

Wasm can target not only the browser, but also other JavaScript runtimes like Node.js. In the future it will become much more important as a lightweight container runtime alternative to Docker for running service workloads.