Creating a library for use from JS with js_of_ocaml

Hi everyone. I inherited a toy webapp written in javascript and I’d like to rewrite the actual logic as a self-contained OCaml library, which I’d compile using js_of_ocaml and I’d call from JS for display. However I’m having trouble making it work. Could someone explain what is the appropriate way to configure this? I’m not very familiar with javascript and with javascript modules in particular so I might be missing something obvious.

(Also, please tell me if you don’t think this is a reasonable thing to try to do.)

Here is my simplified set-up at this point:

myapp/
|- index.html
|- main.js
|- ocaml/
   |- dune
   |- export.ml
   |- export.mli
   |- code.ml
   |- code.mli
   |- export.bc.js

code.ml and its interface are a self-contained library module. Taking inspiration from the documentation, export.ml contains this:

open Js_of_ocaml

let () =
  Js.export "mylib"
    begin object%js
      method myfunction x = Code.myfunction x
    end end

export.mli is empty. The dune file generates export.bc.js as follows:

(executable
  (name export)
  (promote)
  (modes js)
  (preprocess (pps js_of_ocaml-ppx)))

Finally, main.js (which is included in index.html in module mode) tries to import the library code:

import {mylib} from "./ocaml/export.bc.js";

This fails: mylib doesn’t exist.

So, can anyone see what’s wrong with what I did?

2 Likes

Js.export is not an export clause, so it seems fair that the import clause would fail. You need to use a require clause instead, i.e., require "./ocaml/export.bc.js".

1 Like

Ok, thank you. I am not using node so I can’t use require but what I found is that using the import function works:

import("./ocaml/export.bc.js");
var y  = mylib.myfunction(x);

I had not understood that there was a difference between dynamic exports and static ones (the static ones are a more recent idiom/feature right?). Just out of curiosity, can you generate static exports with jsoo?

Right, modules are surprisingly static for a language like Javascript. In particular, the import and export static directives are entirely handled at parsing time, so they cannot depend on some dynamic content (e.g., no conditional import/export). And yes, modules are a rather recent feature.

Not that I know of. I suppose that you could write a script that scans the generated Javascript and produces a wrapper:

import foo from "./ocaml/export.bc.js"; // actually a dynamic import, despite the syntax
export let myfunction = foo.mylib.myfunction; // static export
1 Like

Cautionary note for anyone reading this in the future: dynamic imports are asynchronous, and initializing the jsoo runtime takes some milliseconds, so that if you just do:

import("ocaml/export.bc.js");
var x = mylib.myfunction();

the second line will fail as mylib is not defined yet (at least this is what I think is happening). You need to guarantee the module is done initializing in some way or other.

2 Likes

import should return a promise of the loaded module. So you can just await for it (if your current context allows you to write await) or just :

 import("ocaml/export.bc.js").then ((_) => {

 mylib.myfunction();

});

Yup, I just put my code inside the .then and it worked fine.