How to package Javascript stubs / support code

Background: I’m trying to create OCaml (js_of_ocaml) bindings to the tree-sitter.js library. My repository is at GitHub - joelburget/brr-web-tree-sitter: Ocaml (Brr) bindings to web-tree-sitter.

Question 1: Reading through the opam manual, I don’t see any mention of js_of_ocaml, minimal mention of stubs, and the document assumes architectures are ’ typically one of "x86_32" , "x86_64" , "ppc32" , "ppc64" , "arm32" or "arm64" , or the lowercased output of uname -m , or "unknown"'. How does the distribution of js_of_ocaml-specific libraries typically work?

I attempted to add the tree-sitter.js library using dune sites in Work on using dune sites. · joelburget/brr-web-tree-sitter@8354580 · GitHub. Unfortunately this didn’t work, I believe because js_of_ocaml removed the library when linking. Quoting the docs:

Primitive code must be annotated with the primitive name and primitive requirements. The linker uses these information to only include the primitive actually used in the program and to perform better deadcode elimination.

I attempted to fix this in two different ways in

  1. Adding a line joo_global_object.TreeSitter = TreeSitter; to the end of tree-sitter.js. This didn’t have any effect I could see.
  2. Adding external _tree_sitter_fake_primitive : unit -> unit = "tree_sitter_fake_primitive") in.

Unfortunately dune build gives:

Undefined symbols for architecture x86_64:
  "_tree_sitter_fake_primitive", referenced from:
      _caml_builtin_cprim in camlobj2b27a0-2f17c0.o

I tried adding (modes js) to the library, but get “Error: Unknown value js” (I guess this is only available for executables?).

Question 2: What’s the right way to provide bindings to a JavaScript library on opam? What’s the difference between my library and GitHub - janestreet/zarith_stubs_js: Javascripts stubs for the Zarith library such that zarith_stubs_js works?

Hi @joelb,

Quick disclaimer, I haven’t actually released any jsoo bindings to Javascript libraries. But hopefully I can help a bit :))

I think js_of_ocaml support in opam is lacking, I don’t think there is a way to specify that a library should only be used in the context of compiling to javascript. If it is a binding library, it will have a dependency most likely to js_of_ocaml but that’s probably about it. There’s nothing in opam yet that I’m aware of to mark a library as jsoo-compatible (by library here I mean plain OCaml code not a binding to a JS library).

As I see it the current way to do this is just to release the library like any other library. When compiling an actual application with dune using executable and (modes js), it is then up to the user to provide the javascript library which could be:

You could then document how a user should provide the JS library implementation that your library binds.

Alternatively, the library stanza does support the js_of_ocaml field (see Stanza reference — dune documentation). I believe this allows you to vendor the Javascript library in your repository and link it to the library so the user wouldn’t have to provide it at all. This allows you to write bindings for a specific version of a library and ensure the user gets the correct JS. See for example the bindings to leaflet here: openEngiadina/geopub: An XMPP client for geospatial data - src/leaflet/dune at main - geopub - I haven’t actually seen anything released like this in practice, but I think it should work.

The difference is you are binding a Javascript library whereas zarith_stubs_js provides Javascript implementations for Zarith’s C functions. For example, the C function ml_z_div and the JS counterpart.

Thank you @patricoferris, your answer is very helpful.

Using a .js file with the joo_global_object approach like you pointed out

I want to make sure I understand the approach you’re taking here. JSOO outputs index.bc.js, which includes require("@codemirror/view"), require("@codemirror/state"), etc. You then rely on Parcel (or Webpack, etc) to link the JS. Does that sound right?

This approach sounds very appealing since it’s simple on the OCaml side. It made your Codemirror bindings possible (which I could never figure out how to do). More generally, it solves the problem of working with JS modules from OCaml. I see two downsides:

  1. It’s an extra step for users
  2. Makes it possible to link an incompatible version of the JS library (as you noted)

By the way, I noticed @jchavarri debating some of the same questions: Should jsoo-react vendor react.js? · Issue #104 · ml-in-barcelona/jsoo-react · GitHub.

1 Like

Yep exactly, the examples are broken in that code-mirror repository as you pointed out (thanks for the issue), but they should be working in this editor application using npx esbuild in a dune rule.

Thanks for pointing out that jsoo-react issue, I like the comment:

I guess it all comes down to how “close” to JavaScript ecosystem a project is: for projects leveraging a lot of JS related tooling, using Webpack or other bundler is not a pain (actually helps). But for projects leaning more on OCaml side, with very few JavaScript dependencies, adding bundler is actually overkill.