Hot-reloading OCaml on Web, Desktop, and Android
General program setup
If you hot reload one thing, you'll need to reload everything that depends on it -- otherwise you get type errors.
So if we picture our program as a DAG, you'll need to reload a whole chunk. In the case of reprocessing, the chunk that you reload is "all of the user's modules", while the library modules don't get reloaded.
Another important thing is that there needs to be a "hooking in point", where the newly loaded modules can make themselves known, and this point has to exist in the "not hot reloaded " portion of things. For reprocessing, that point is
Reprocessing.run(), which is the main starting point of the game. When the user's code gets reloaded, it calls
Reprocessing.run again, and Reprocessing notices that this wasn't the first time, so it preserves state and swaps in the new
This one is the most straightforward.
You rebuild the
.cmos for each module that you want to reload using
ocamlc -c, and combine them into a
ocamlc -a. Note that the order they're put into the .cma is important! They must be in dependency order, which you can get by running
Notify the running process that it should hot reload (either by having the process periodially check the
mtime of the
.cma, or via a socket, or whatever), and then do a
And that's the size of it!
Android requires some more work, because you have to get the
.cma file to your device.
I set up a basic TCP server that watches the files for a change, and on successful rebuild makes a
.cma for any waiting clients.
The client is written in Java because I didn't want to mess with getting the
Unix library working on Android. It attaches to the TCP socket, sends over information about what file it wants hot reloaded & the current architecture, and then waits. When the server sends over a new
.cma, it writes it to a file & then notifies the ocaml side so it can run
Bucklescript doesn't have
The first time through, I do a full build, which creates a global
packRequire along with a mapping of filenames to package IDs. Each successive time, I bundle with the flag
external-everything, which only bundles local files, and defers to the global
packRequire for all non-relative requires (e.g. requires that would be searched for in
I then have a simple script that tries to fetch and evaluate
/hot/bundle.js in an recursive loop. The server, when it gets a request there, keeps the client open & waiting until there is a change, at which point it creates a new bundle and fulfills the request with it.
I've had some musings about making a library that will take care of the bones of hot reloading for you automatically... we'll see where that ends up.