This post is a result of a game I made in OCaml for js13k game jam. I will share some observations I had when creating the game.
But first let me briefly introduce js13k.
Since this was my first time using OCaml, I wanted to build something simple to make sure I was going to make it and wouldn't go beyond the required size. The theme for this year was "404 not found", so i created a guessing game in which the player needs to reveal a word or a phrase by guessing letters - similar to the game of hangman. This was pretty basic in itself, so I've added animations after each correctly guessed phrase. Each phrase has it's own animation and this turned out to take about 1/4 of the code size.
'a' + 1 // 'a1' 'a' - 1 // NaN 'a' + NaN // 'aNaN'  ==  // false  == ! // true
All of these are either highly surprising or don't make any real sense.
In contrast, OCaml is statically and strongly typed language. This results in constructs like
"a" + 1 simply being a compilation error. It goes even further, because even adding an integer to a float doesn't compile - you need to cast both arguments to the same type. This simplifies development, because it yields no surprising behaviour. Refactoring is also much simpler, because compiler checks the correctness at least partially.
It plays well with OCaml tooling, you can install it using opam package manager with command
opam install js_of_ocaml js_of_ocaml-ppx
js_of_ocaml is the base package
js_of_ocaml-ppx provides syntax extensions for OCaml code. There are a few more packages that provide additional functions.
Once installed, you first compile OCaml code to byte code as you'd normally do, for example with
ocamlfind ocamlc -package js_of_ocaml -package js_of_ocaml-ppx -linkpkg -o script.byte script.ml
and then you invoke
Functions provided by
js_of_ocaml are split to several modules:
module Dom_html - DOM HTML bindings
module Firebug - debugging console
and many other
All functions operate on types provided by
js_of_ocaml, which among other are:
optdef are special types for handling null and undefined values:
opt represents a possibly null value,
optdef - possibly undefined value. As such, there's no notion of type that can be both null and undefined.
All types in
js_of_ocaml package are designed in an object oriented way, so you call methods and read/set properties on types. Type signatures of these types are somewhat complex, so to ease that you should use syntax extensions provided by
js_of_ocaml-ppx package. This gives you syntax that is pretty similar to normal object oriented OCaml code:
element##method_name arg1 arg2 (* method call *)
let foo = element##.property (* read value of a property *)
element##.property := new_value (* set new value for a property *)
For example, here I call
querySelector to get all elements that have class
Html.document##querySelector (Js.string ".big")
Here's how I can get current color of some DOM element
let color = elem##.style##.color
And I can change current colour to, e.g. "black":
elem##.style##.color := Js.string "black"
I can also assign event handlers:
Html.document##.onkeydown := Html.handler keypressed
Here's some less trivial example - a function that adds a CSS class to a given element:
let jstr s = Js.string s let nullstr () = jstr "" let addClass (elem: Html.element Js.t) cls = let class_str = (jstr "class") in let current_cls = (elem##getAttribute class_str |> Js.Opt.get) nullstr in (* 1 *) let new_cls = current_cls##concat_2 (jstr " ") (jstr cls) in (* 2 *) elem##setAttribute class_str new_cls (* 3 *)
(* 1 *) I get current CSS classes assigned to the element. The
getAttribute method results in a
Js.Opt type, so it can be null. To get the actual value I pipe it to
Js.Opt.get function and it returns the stored value if it's there. Otherwise it returns the value returned by
nullstr function, which is empty string.
(* 2 *) I append the new class to the class list from
(* 1 *).
(* 3 *) I finally set the newly constructed string as current for the element.
One thing to keep an eye on is the resulting package size. When all you have is 13kB, even bytes can count. Unfortunately,
js_of_ocaml adds about ~6kB of overhead upfront. That's quite a lot, especially since it doesn't provide any extra features to create a web based game. And that can grow even more if using some additional OCaml functions. When I tried to use
Printf.printf function, resulting package size increased by ~15kB. I had to fall back to simple string concatenation.
One more case when I had an issue with code size was when I had to compare OCaml string with
Js.js_string. To do that I needed to convert one of them so that both have the same type. When I converted OCaml string to
Js.js_string package size was bigger than when I converted
Js.js_string to Ocaml string. The difference was ~4kB, so was definitely noticeable.