# An Introduction to js_of_ocaml If you want to write web apps using OCaml, then there's a good chance you'll want to use js_of_ocaml. [js_of_ocaml](https://ocsigen.org/js_of_ocaml/latest/manual/overview) - which will be abbreviated as "jsoo" from here on out - is a compiler that turns OCaml sourcecode into Javascript. From the [source README](https://github.com/ocsigen/js_of_ocaml): > * It is easy to install and use as it works with an existing installation of OCaml, with no need to recompile any library. > * It comes with bindings for a large part of the browser APIs. > * According to our benchmarks, the generated programs runs typically faster than with the OCaml bytecode interpreter. > * We believe this compiler will prove much easier to maintain than a retargeted OCaml compiler, as the bytecode provides a very stable API. This is a tutorial for getting started with jsoo by building a small interactive animation in the browser: ![](https://i.imgur.com/iN6b7I2.gif) We're going to need some way to build our project, and this tutorial was written with the popular OCaml build tool [`dune`](https://dune.readthedocs.io/en/stable/) in mind. Though, instructions for compiling with another build tool [`ocamlc`](https://ocaml.org/manual/comp.html) can be found in the jsoo [manual](https://ocsigen.org/js_of_ocaml/latest/manual/overview). If you're new to dune you can read more about it [here](https://dune.readthedocs.io/en/stable/), along with the instructions for [compiling to JavaScript](https://dune.readthedocs.io/en/latest/jsoo.html). Our final product is an interactive animation that renders in a browser window like so: All the code in this post can be found [here](https://github.com/InfiniteSwerve/jsoo_intro). **Note:** Please give me feedback! If I've missed something important, phrased something poorly, or said something incorrect, comment and let me know! ## Setup To start, we're going to install some things from the jsoo library: `opam install js_of_ocaml js_of_ocaml-ppx js_of_ocaml-lwt` Our file structure is simple here: ``` JSOO_INTRO/ |------ _build |------ main.ml |------ index.html |------ dune ``` The `_build` file will be generated once we build our program. We can leave our `main.ml` file blank for now. But we're going to use our `index.html` file to run the script we generate with jsoo: ```htmlembedded= <html> <head> <title>Jsoo_intro</title> <script type="text/javascript" src="_build/default/main.bc.js"></script> </head> <body> </body> </html> ``` This code from line 4: `src="_build/default/main.bc.js"` is to tell our html file where to get our javascript from. In the case of my build path, `main.bc.js` lands here. Make sure to substitute your own path if you're not using dune. Our `dune` file looks like the following: ```ocaml= (executable (name main) (modes js) (preprocess (pps js_of_ocaml-ppx)) (libraries js_of_ocaml js_of_ocaml-lwt js_of_ocaml-lwt.graphics)) ``` To make sure everything is working run `dune build ./main.bc.js` **Note:** A `.bc.js` file functions identically to a `.js` file. The jsoo build path first turns OCaml source code into byte code (`.bc`), then the jsoo compiler turns that bytecode into javascript (`js`). Hence, the final product, (`.bc.js`) signals that the javascript was compiled from bytecode. ### The DOM Before we can use jsoo, we need to know a bit about the DOM. The [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) is an interface between web documents and programming languages. It represents documents as a tree structure, and is how we're going to interact with our html page. To learn more about the DOM, check out [this tutorial](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction). To interact with objects in the DOM using OCaml, we use the following syntax: Accessing methods on objects is done via `##` e.g. `object##method` Accessing properties of objects is done via `##.` e.g. `object##.property` Setting the properties of objects is done via `:=` e.g. `object##.property := newValue` ## Example 1. Basic Display We can start by writing a program which displays "Hello World" in our browser. In `main.ml` we start with some boilerplate by aliasing useful modules. Next, because string representations in OCaml and JS are different, we make a helper function `js_str` which converts OCaml strings into JS strings. Finally, we create a reference for our html document. ```ocaml= module Html = Js_of_ocaml.Dom_html module Dom = Js_of_ocaml.Dom module Js = Js_of_ocaml.Js module G = Graphics_js let js_str = Js.string let doc = Html.document ``` We then make a function which creates a '[canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)'. An html canvas, much like a painter's canvas, is what we paint on: ```ocaml= let canvas_width = 300. let canvas_height = 150. let create_canvas () = let r = Html.createCanvas doc in r##.width := int_of_float canvas_width; r##.height := int_of_float canvas_height; r ``` Next we make a function for drawing our graphics: ```ocaml= let draw_things context = context##strokeRect 0. 0. canvas_width canvas_height; context##.font := js "50px serif"; context##fillText (js "Hello World") 20. 90. ``` Now we make our onload function which pieces things together. Note that in line 5, we need to get the [2D context of our canvas](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) in order to draw on it: ```ocaml= let onload _ = let canvas = create_canvas () in G.open_canvas canvas; Dom.appendChild doc##.body canvas; let c = canvas##getContext Html._2d_ in draw_things c; Js._true ``` Finally, we initialize our program: ```ocaml= let _ = Html.window##.onload := Html.handler onload ``` Compile our file again with `dune build ./main.bc.js` and view see our changes: Ta Da! ![](https://i.imgur.com/o6LT8Ax.png) ## Example 2. Dynamic Updates Now, let's make our program more dynamic by building an incrementing timer using [lwt](https://ocsigen.org/lwt/latest/manual/manual). There are a few changes we need to make. First we include a counter argument for our function. Then we write our counter to the canvas. Finally, we include an Lwt binding at the end of our `draw_things` function. This allows our function to 'sleep' for a second before 'waking up' and calling itself, incrementing the timer. ```ocaml= let rec draw_things c counter = let open Lwt.Syntax in c##.font := js "50px serif"; c##fillText (js "Hello World") 20. 90.; c##strokeRect 0. 0. canvas_width canvas_height; c##.font := js "20px serif"; c##fillText (js ("This page has been open for:")) 20. 110.; c##fillText (js ((string_of_int counter) ^ " seconds")) 20. 130.; let* () = (Js_of_ocaml_lwt.Lwt_js.sleep 1.0 in c##clearRect 0. 0. canvas_width canvas_height; draw_things c (counter + 1) ``` Our onload function also needs one small change. We pipe `draw_things` to an `ignore` like so on line 6: ```ocaml= let onload _ = let canvas = create_canvas () in G.open_canvas canvas; Dom.appendChild doc##.body canvas; let c = canvas##getContext Html._2d_ in draw_things c 0 |> ignore; Js._false ``` Recompile with dune build, and we have a dynamically updating web page! ![](https://i.imgur.com/iz9KB6k.gif) ## Example 3. Adding Interactivity Finally, we'll add a button to our page which resets the counter when we push it. We start by defining a generic button: ```ocaml= let button name callback = let res = doc##createDocumentFragment in let input = Html.createInput ~_type:(js "button") doc in input##.value := js name; input##.onclick := Html.handler callback; Dom.appendChild res input; res ``` Note how on line 3, our input has type "button". This means our rendered input will be a button like so: ![](https://i.imgur.com/9ObvHVA.png) We could change this to a checkbox, text, url, or many other input types. More information on inputs can be found [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). In order to utilize this button, we'll need to change our onload function like so: ```ocaml= let onload _ = let main = Js.Opt.get (doc##getElementById (js "main")) (fun () -> assert false) in let canvas = create_canvas () in let c = canvas##getContext Html._2d_ in let promise = ref @@ draw_things c 0 in G.open_canvas canvas; Dom.appendChild doc##.body canvas; ``` Note that on line 6 we create start our animation, and capture the resulting [promise](https://www.cs.cornell.edu/courses/cs3110/2020fa/textbook/adv/promises.html) in a reference variable. We can now create our reset button: ```ocaml= Js_of_ocaml.Dom.appendChild main (button "Reset" (fun _ -> let div = Html.createDiv doc in Js_of_ocaml.Dom.appendChild main div; Lwt.cancel !promise; c##clearRect 0. 0. canvas_width canvas_height; promise := draw_things c 0; Js._false)); Js._false ``` On line 6 our button cancels the promise we captured in our reference variable, this stops the incrementing timer and allows us to clear our canvas. We then start a new animation, and capture the resulting promise in our original reference variable, that way we can reuse it whenever we press the reset button. Recompile and we get the following: ![](https://i.imgur.com/iN6b7I2.gif) You now have the information and resources to start working with Js of OCaml on your own! ## Further Reading and Practice Here's a link to the [Jsoo documentation](https://ocsigen.org/js_of_ocaml/latest/manual/overview). More generally, if you want to know what's in a library or module, look in the [Ocaml Documentation Hub](https://docs.ocaml.pro/). If you like problem sets, try following this [tutorial on the canvas api](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage) and replicating the examples using Ocaml. If you want to work with something larger, try understanding these [jsoo examples](https://github.com/ocsigen/js_of_ocaml/tree/master/examples) and modifying them. As an example of a full project here's [2048 built with OCaml](https://github.com/ocamllabs/2048-tutorial). [Jane Street](https://github.com/janestreet) builds web apps with a library called [Incr_Dom](https://github.com/janestreet/incr_dom). You can watch a presentation on the library [here](https://www.youtube.com/watch?v=h_e5pPKI0K4). ## Appendix: Viewing Your Work When working with javascript and html documents files, you're going to want some way to see the changes you've made in the browser. For simple programs you can locate your html file and open it in a web browser, then refresh the browser when you want to see changes made. Getting an automatic refresh is doable, but the setup is different depending on what editor you're using: **Visual Studio Code:** * [live-preview](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) **Emacs:** * [skewer-mode](https://github.com/skeeto/skewer-mode) * [impatient-mode](https://github.com/netguy204/imp.el) **Vim & Others:** * [browser-sync](https://dev.to/fidelve/using-vim-as-your-main-editor-for-web-development-5a73)