gen_js_api icon indicating copy to clipboard operation
gen_js_api copied to clipboard

How to bind a js lib without mutating the global object.

Open hhugo opened this issue 1 year ago • 8 comments

gen_js_api seems to advertise mutating the global object in order to make js values accessible on the OCaml side using [@@js.get ], [@@js.global], ...

For example, cat node-test/bindings/imports.mli

[@@@js.scope "__LIB__NODE__IMPORTS"]

val path: Ojs.t [@@js.global]
val fs: Ojs.t [@@js.global]

cat node-test/bindings/imports.js

globalThis.__LIB__NODE__IMPORTS = {
  path: require('path'),
  fs: require('fs'),
};

This causes problems when loading multiple js files generated by jsoo because one could override a global value with an incompatible one , see https://github.com/ocsigen/js_of_ocaml/issues/1622 and https://github.com/ocamllabs/vscode-ocaml-platform/issues/1617.

I'm opening this to understand if this issue has already been solved, if it has already been identified and discuss possible solutions.

Note that jsoo is able to bind to js libs relying on external primitives without mutating the global object.

hhugo avatar Nov 19 '24 09:11 hhugo

Gentle ping @mlasson

smorimoto avatar Dec 23 '24 23:12 smorimoto

Hello!

Thank you for the ping, @smorimoto, and apologies for the delay in responding to this issue.

You’re absolutely right—we need a solution that avoids mutating the global object.

Here’s a proposal that relies on stubs like imports.js:

// Provides: node_path
var node_path = require('node:path');

// Provides: node_fs
var node_fs = require('node:fs');

And the corresponding OCaml interface in imports.mli:

[@@@js.scope Ojs.runtime]

val path: Ojs.t [@@js.global "node_path"]
val fs: Ojs.t [@@js.global "node_fs"]

Here, Ojs.runtime would serve as an alias for (pure_js_expr "globalThis.jsoo_runtime").
More generally, I’m not aware of any established "good practice" for writing a js_of_ocaml library that loads modules using require in its stubs. Do you know of any examples that follow such an approach?

mlasson avatar Jan 13 '25 14:01 mlasson

If we go this route, perhaps it would be a good idea to ask for "globalThis.jsoo_runtime" to be exposed as part of the Jsoo_runtime module ?

mlasson avatar Jan 13 '25 15:01 mlasson

I don't think you want to use globalThis.jsoo_runtime. It's still part of the globalObject and won't allow you to have multiple jsoo programs running at the same time [1].

I'm working on adding the following function val Jsoo_runtime.external_ : string -> 'a (I'm not sure about the name) to give access to runtime functions/values without relying on external ocaml definition.

Maybe gen_js_api could introduce a new attribute for that

val path: Ojs.t [@@js.runtime "node_path"]
val fs: Ojs.t [@@js.runtime "node_fs"]

[1] jsoo currently mutates the global object when doing separate compilation which can prevents running multiple jsoo programs at the same time. Whole program compilation doesn't have that limitation (unless dynlink or --extern-fs is used).

hhugo avatar Jul 28 '25 12:07 hhugo

ping @mlasson

smorimoto avatar Aug 25 '25 15:08 smorimoto

Ok ! I will make a PR soon to implement @hhugo 's suggestion to use Jsoo_runtime.Sys.external for expanding a new attribue. And I'll merge it if you like it and if ocsigen/js_of_ocaml#2086 gets merged as well.

mlasson avatar Aug 27 '25 08:08 mlasson

Ok ! I will make a PR soon to implement @hhugo 's suggestion to use Jsoo_runtime.Sys.external for expanding a new attribue. And I'll merge it if you like it and if ocsigen/js_of_ocaml#2086 gets merged as well.

Done in https://github.com/LexiFi/gen_js_api/pull/183

mlasson avatar Aug 27 '25 16:08 mlasson

@hhugo gentle ping

smorimoto avatar Sep 12 '25 20:09 smorimoto