Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bindings with requires #66

Closed
Aaylor opened this issue Nov 14, 2016 · 6 comments
Closed

Bindings with requires #66

Aaylor opened this issue Nov 14, 2016 · 6 comments

Comments

@Aaylor
Copy link

Aaylor commented Nov 14, 2016

Considering a javascript library containing a module A with only one static
function called foo taking a string and returning an int. This library
has to be required before usage, but I did not found anything about require in
the documentation.

The first implementation I made was:

module A : sig
  type t
  val require_a : unit -> t
    [@@js.custom let require_a () = Helpers.require "A" ]
  val foo : t -> string -> int
end

To call the function foo in ocaml code, I have to write:

let module_a = A.require_a () in
let i = A.foo module_a "bar" in
...

But I'd like to get rid of the require line. This require would be done lazily into the
module, allowing the user to write directly let i = A.foo "bar" to call the
function.

My first thought was to do something like:

module A : sig
  [@@@js.implem
    let mod_a = ref None
    let get_a () =
      match !mod_a with
      | Some m -> m
      | None ->
        let m = Helpers.require "A" in
        mode_a := Some m;
        m
  ]
  val foo : string -> int
  [@@js.custom
    let foo str =
      let m = get_a () in
      let r = Ojs.call m "foo" [|(Ojs.string_to_js str)|] in
      Ojs.int_of_js r ]
end

But it becomes very annoying to write this when the javascript library has many
methods. What would be the best way to do this kind of things ? Does it match
the philosophy of javascript bindings ?

@alainfrisch
Copy link
Collaborator

First, even with the manual encoding, you could use gen_js_api to produce a binding for:

  type a_module = Ojs.t
  val foo_internal: a_module -> string -> int

and then simply define the user-visible foo in term of this foo_internal:

  let a = lazy (...)
  let foo x = foo_internal (Lazy.force a) x

Now, assuming that a Javascript global variable A_module is bound to the result of require("A") (either with manual JS code, or through gen_js_api), you can also directly expose:

  val foo: string -> int [@@js.global "A_module.foo"]

One could also design some built-in gen_js_api support for this scheme, perhaps bound to OCaml modules:

module A : sig
  val foo: string -> int
end [@@js.require]

within a module marked with the js.require attribute, all functions would implicitly be assumed to be on the object resulting from a call to require (by default, with a name derived from the module binding, with a way to override that).

@Aaylor
Copy link
Author

Aaylor commented Nov 15, 2016

Thanks for your answer !
I wrote the ml file instead of the mli using an internal representation, allowing me to hide the internal module with the mli file:

module A_internal : sig
  type a_internal = Ojs.t
  val foo : a_internal ->int -> int
end

let mod_a = lazy (Helpers.require "a")
let foo i = A_internal.foo mod_a i

Implementing such an attribute could be a great idea to avoid code duplication or errors. Would you see this in gen_js_api ? I could give a try to add this attribute.

@alainfrisch
Copy link
Collaborator

Would you see this in gen_js_api ? I could give a try to add this attribute.

Yes, absolutely! If some "small" additions to the tool simplify supporting common idioms in bindings, they are very much welcome.

@jchavarri
Copy link
Contributor

jchavarri commented Aug 31, 2019

I picked up the branch in #68 and updated it with latest master to explore the generated code for require attributes. One challenge is that in development mode, gen_js_api (in particular, these changes in ojs.ml) produces the following output after running through jsoo:

var internal_require=require;
function require$0(name){return internal_require(name.toString())}

This is a no-go for most common JavaScript bundlers like Webpack, that need the string passed to require to be a literal so it can be resolved at bundling time.

If I compile using the --profile=prod flag in Dune, then all these map functions like .toString get inlined so the expressions are resolved with literals, but I wonder if there's a way to "move forward" this expression generation so the resulting code can contain the string literal already?

Aside: I can create a PR with the updated branch if it helps.

@Lupus
Copy link

Lupus commented Feb 16, 2020

Writing quite some Node bindings - and it's annoying to bind modules to some globals and then reference those in @js.global. It's still not bundler friendly, but I have to do it manually...

@mlasson
Copy link
Member

mlasson commented Aug 17, 2021

Closing due to inactivity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants