JSOO build integration with JS front-end project?

I want to use JSOO to build a JS package that contains core logic that will run in a browser, presenting a JSON-y API to be consumed by JS front-end code. This seems like a good way to use js_of_ocaml: all the complicated JS UI stuff doesn’t need to be replicated in OCaml, but you can still keep your core domain logic there.

I’ve got dune building my .js using JSOO and exposing functions to JS. If I copy that JS by hand to my front-end project, it works. Now I want to be able to:

  • Expose the output as something that my front-end project can bundle without a manual copying step
  • Be able to make changes to in a dev environment and see the changes quickly in a browser

To do this with ReScript compiling my core .ml syntax project, I have esbuild running under a watcher + rescript build -w. I run rescript build -w on my front-end, with the core project npm linked in. Any change I make to either project (core or front-end) shows up effectively immediately in the browser.

Has anybody got advice on how to get a similar setup working with JSOO compiling the core project in place of ReScript, and JS on the front end? Ideally on github, but otherwise hints appreciated!

2 Likes

Hi @currycomb

I took a stab at implementing something that could maybe be helpful, apologies if I misinterpreted what you were asking, the repository is jsoo-reload-example. I think the README explains it quite well, but for completeness I’ll add the general idea below.

To achieve this I used the a dune rule with a copy action. The dependency is the output of the jsoo build (main.bc.js) and the target is a new js file (print.js) in my project directory. It is promoted automatically into that file and so if the jsoo is recompiled, this copy happens right afterwards. I didn’t commit the file but you could.

Now thanks to the previous setup running dune build -w will recompile the jsoo and copy it over whenver you make a change to files your jsoo project requires.

All of the next part may not apply to your use case, but others might find it somewhat useful. I used the parcel bundler (this is the only bundler I know vaguely how to use) to then take the project and bundle it into a dist directory. Running parcel watch site/index.html concurrently with dune build -w now sets up a whole pipeline from making a change to the jsoo to having a fresh, bundled output.

The final step for me (and again this probably doesn’t apply) was to have live-reloading server serve the contents of the bundled output. Using dream, dream-livereload and irmin-watcher (an OCaml library for watching the filesystem) I was able to achieve this (I’ll leave out the details but everything is in the server directory), so now we have:

  1. dune build -w notices a change to the jsoo and recompiles.
  2. dune also then copies the jsoo output over to the project directory (in the example called site).
  3. The site directory has now changed triggering parcel to bundle the output to dist.
  4. dist has now changed triggering irmin-watcher which using dream and dream-livereload uses websockets to trigger a browser refresh.

That list should also set your expectations for how fast this all should be… not blazingly fast, but sure beats manually copying and restarting things by hand, hope it helps :))

6 Likes

You might be able to use (1) with “parcel serve”, if hot module replacement is desired.

1 Like

Amazing, thank you so much! I didn’t know dune was capable of this sort of thing, and I couldn’t have asked for a more complete how-to.

I’m keen to try at least the dune part in my project the moment I get through my long list of boring tasks for today :slight_smile:

1 Like

Glad it could hopefully be useful, I was just implementing something that was very similar to this for my own personal project and realised there’s a pretty important point that I missed. By default dune build uses the dev profile for jsoo applications which compiles modules separately and then links them (meaning there’s no deadcode optimisation) and includes things like source maps which is very useful for development. But if you are committing the results of the copy I would suspect you want the output of a dune build --profile release. For example, in my project with very modest amounts of js this was the difference between having 3.3MB of js and 0.06MB

As far as I am aware, there’s no way to specify the release profile for a stanza that generates JS. So the only way to get this to always compile the jsoo in release mode is to write the dune rules by hand (i.e. compile to bytecode, then compile the bytecode to js using the js_of_ocaml compiler directly). For me I just always run dune build --profile release. Some dune experts might be able to chime in on a better way to do that :))

If you want to change the default profile from dev to release, you can do that with a dune-workspace file which contains this:

(lang dune 2.9)
(profile release)

You can choose your dune version according to requirements (dune-2.9 happens to be the latest, and you may well want to go for an earlier version).

1 Like

Hmm, I’m finding that the release JS from JSOO is ~73kb. It seems to embed the whole runtime. It definitely mangles/minifies symbols, but it’s all still in there, including a bunch of node stuff, perhaps merely because there is a common.js export a la Export OCaml code to JavaScript. Searching around the ocisgen site doesn’t yield breadcrumbs on producing web optimized exports. :thinking: Curious if JSOO has web optimized exports? Will be jumping into the source soon :slight_smile:

2 Likes

Jsoo doesn’t include the whole js runtime. It include what it needs. It could be reduced by dropping node support though.