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 draw
function.
Native desktop
This one is the most straightforward.
You rebuild the .cmo
s for each module that you want to reload using ocamlc -c
, and combine them into a .cma
with 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 ocamldep
.
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 Dynlink.load("./path/to/the.cma")
.
And that’s the size of it!
Native Android
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 Dynlink.load
.
Web
JavaScript is quite different.
Bucklescript doesn’t have dynlink
built in, but I built my own JavaScript bundler, and I implemented a feature similar in spirit to browserify’s “externals”.
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 node_modules
).
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.
That’s 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.
Catch me on twitter or the reasonml discord channel.