JSOO including modules it shouldnt

I am working on a library that has modules that target both native and the browser. But when I try to run the browser module, I get JSOO runtime errors about native functions not existing.

If I have a module Core, and it has two sub-modules A and B, when I open Core.B in a script then the functions from A shouldn’t be included right?

Does anyone have any idea what’s going on here or how to prevent it?

1 Like

What do you mean by “native functions”? If you mean C primitives, then you must provide a JS implementation for them.

Cheers,
Nicolas

I mean functions that are not meant to target the browser.

In this case, they aren’t included in the module I’m importing into the program compiled with JSOO so I don’t know why, or how, it’s including them.

It is hard to know what we are talking about without more details. A precise error message, for example, would help.

Cheers,
Nicolas

I have a library, and it has a module Core, it holds two other modules A and B that expose functionality to the user. Core.A is written with JSOO, and is meant to be used in the browser, Core.B is native-targeted code that will not run in the browser.

When I write an example importing Core.A, build it, and run the Javascript in my browser I get this error:

Uncaught TypeError: runtime.caml_cairo_font_type_init is not a function
    at cairo.ml:501:10

This doesn’t make sense because there is nothing related to Cairo in Core.A, Cairo functionality is in Core.B and even then, that doesn’t include anything relating to fonts.

Does that make sense? It feels like this I an odd problem to be having, so I get that it may be sort of confusing.

This is the part I don’t understand. There’s no importing in OCaml, so either you’re importing in another language something that happens to be implemented in OCaml, or you’re either opening the module or referring to it by path.

The issue is likely that you’re building an application that includes both the native and JS code, because if Core is a library with submodules A and B you likely can’t link with one submodule but not the other (although there are some tricks that might help).

Either way, if we don’t know what your build system and project structure look like there isn’t much more that we can say.

Does open really differ from say Rust’s use or Javascript’s import to the degree that you’d say OCaml does not have importing?

Maybe that’s the issue here, my understanding is that you open a module and everything in that module is available in the file the open statement is in. Not that open includes modules from elsewhere in the module tree, when I haven’t specified that I want to include those modules.

There has to be a way to only include the module you want and not the entire module tree, right?

It’s a very simple OCaml project, almost identical in structure to what dune init project produces. There’s a lib where the actual implementations live, an examples folder where examples live, and a test folder where tests live. I am using Dune exclusively.

Can you upload a repro project somewhere like GitHub?

Yes.

Two things that might help:

  • OCaml is a language with greedy evaluation, not lazy (or “by need”). This shows up in the language itself, but also at the project level: if you do open Core.A, then the tools ensure that the Core module exists and is fully evaluated, then extract its submodule A, then make its contents available in the current scope. So if Core happens to also have a submodule B, it will have been fully evaluated as part of the evaluation of Core.

  • You can split a project into several parts using various abstractions (libraries, files, modules), but the compiler cannot split dependencies at a level finer than the compilation unit (in practice, a compilation unit is a file). So if you have several pieces of code in the same file, you cannot have your final application include only one part and the other.

Given my answers above, the first step is to make sure that the modules you want to separate sit in their own files. Typically, core_a.ml and core_b.ml, although you can name them however you want. Then, open Core_a doesn’t require evaluation of Core_b anymore.
Next, if you still want them to be available under the path Core.A and Core.B, you’re going to hit the first issue, but there is a workaround: the language supports what we call module aliases, which allow re-declaring modules without introducing an actual runtime dependency.
In your example, here is how it could look in core.ml (if you have a core.mli file, it must contain the same lines):

module A = Core_a
module B = Core_b

Now, in the users of the library, open Core.A evaluates Core fully, but thanks to the module aliases feature this does not need to evaluate either Core_a or Core_b. Then, looking up the submodule A of Core resolves the alias to Core_a, so the tools will also ensure that the Core_a module is evaluated, and you can then use it in the rest of your application. Compared to open Core_a, you still have the Core module that is also required, but if it only contains the alias definitions this may not matter much.

2 Likes

You are probably linking with ocaml-cairo. The module Cairo contains

This means that at initialization time, the function font_type_init is called. This function is implemented as a C primitive, and so is not available in the browser. If you want to make it work you must either

  1. provide a dummy stub for it written in Javascript
  2. make it so that you do not link Cairo in your JS executable.

Cheers,
Nicolas

1 Like

This did it, thank you!