Using an external JavaScript file in js_of_ocaml

I am a beginner at both Javascript and js_of_ocaml, so I may be mixing up all sorts of mistakes and misconceptions here.

I have compiled up an existing project, my command line PDF tools, using js_of_ocaml, and all is well:

$ node cpdf.js -info hello.pdf
Encryption: Not encrypted
Permissions:
Linearized: false
Version: 1.1
Pages: 1

Like magic! But I had to comment out the parts of my code which use external C code of course - that is zlib and some encryption primitives. So now I wish to bind javascript libraries for those. I am experimenting with a simple library of my own, first, which is given on the command line to js_of_ocaml as foomod.js:

foo = 42;

I can get to this global variable easily from OCaml:

let foo = Js.Unsafe.global##.foo

But now I want to do things better, and I change foomod.js to:

exports.foo = 42;

How can I get to that? Giving foomod.js on the js_of_ocaml command line includes the contents of foomod.js in some way, but does not contain the string foomod, so I’m not sure how to get to the foomod’s variables and functions. How to I access them? In the node REPL, I can simply do:

> foomod = require('./foomod.js');
{ foo; 42 }
> foomod.foo;
42

I have read the js_of_ocaml help page on how to bind JS modules:

https://ocsigen.org/js_of_ocaml/latest/manual/bindings

I imagine if I could get over this hump, all the rest of the information I need will be there.

Not exactly what you asked, but if you just want to provide a JS version of some C primitive

external foo : unit -> int = "caml_foo"

you can do this by writing the following in your .js file:

//Provides: caml_foo
function caml_foo() {
  return 42;
}

Then js_of_ocaml will automatically replace calls to the external function by a call to its JS implementation.

This is the same mechanism used by js_of_ocaml to implement its own JS version of the OCaml runtime, see eg

Cheers,
Nicolas

3 Likes

Thanks, Nicolas. I think this is enough to get me started.

I would still be interested in the namespacing issue though. What happens if foo.js and bar.js, both given on the command line, both define a global x?

To answer my own question: a global in b.js here overwrites one of the same name in a.js

js_of_ocaml a.js b.js c.byte

I am making progress, having successfully linked in the sjcl encryption primitives library by appending the following line to its .js source:

joo_global_object.sjcl = sjcl;

But now I am trying to do the same with pako, a zlib replacement. And I get:

ReferenceError: pako is not defined
    at /Users/john/repos/cpdf.js/cpdf.js:352:520
    at Object.<anonymous> (/Users/john/repos/cpdf.js/cpdf.js:352:525)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47

It seems to work with

joo_global_object.pako = globalThis.pako;

But js_of_ocaml can’t find it. The beginning of pako.js has this, seemingly common JavaScript preamble:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pako = {}));
}(this, (function (exports) { 'use strict';

How can I export pako to the jsoo_global_object?