ocsigen / js_of_ocaml

Compiler from OCaml to Javascript.
http://ocsigen.org/js_of_ocaml/
Other
953 stars 186 forks source link

[FEATURE REQUEST] allow ECMAScript import and export when defining jsoo stubs #1532

Open hhugo opened 10 months ago

hhugo commented 10 months ago

This this possible now that #1529 is here.

Fizzixnerd commented 4 months ago

I'd like to see this feature implemented. I have lots of functional programming experience, though I'm new-ish to ocaml (I come from a mostly Haskell and F# background). I'd like to give this a shot, but would need some direction definitely to at least getting started. If anyone is able to provide such direction, I would be greatly appreciative. I cannot seem to find out where on Matrix/IRC I should go for a more realtime chat experience either, but that could be because no such channel exists. I will think a bit about a more details spec on what I'd imagine this feature to look like.

Fizzixnerd commented 4 months ago

One of the first things I think needs to be decided is: can we treat ES6 static module imports as OCaml modules? Harder still: should we?

Ultimately, I currently think we shouldn't. It is unclear if it's even possible today, and it's certainly unclear if it will remain possible indefinitely in the future as ES changes. ES is fundamentally a more dynamic language than OCaml, and I think just accepting that and thinking of ES modules as more akin to (ocaml) objects than (ocaml) modules is probably for the best.

Instead, I think a general ES module should be represented as an OCaml object, and so the "types" of ES modules are just classes.

That is, binding to an ES module should be as easy as declaring an OCaml class (or perhaps class type) stub.

As an example taken from https://pixijs.com/8.x/playground?exampleId=basic.container of something I'd ultimately like to be able to do:

open Js_of_ocaml

(* For application below *)
type init_args = {
    background : js_string optdef;
    resizeTo : js_any optdef
}

(* Binding for the Application class *)
class%js.class type application = object 
    method init : init_args Js.t -> unit js_promise
    val stage : js_any (* etc. *)
    val ticker : js_any (* etc. *)
    val canvas : js_any (* etc. *)
    (* ... *)
end

class%js.class type assets = .. (* ... *)
class%js.class type container = .. (* ... *)
class%js.class type sprite = .. (* ... *)

(* Uses the same rules for underscores as described in https://ocsigen.org/js_of_ocaml/latest/manual/bindings *)
class%js.module type pixi = object
    val _Application : application
    val _Assets : assets
    val _Container : container
    val _Sprite : sprite
    (* Any object that extends this type would be okay too, which is nice for writing stubs! *)
end

(* I added an annotation/extension since it might be easier to label these in terms of lifting them all to the top of the file or whatever... *)
let%js.import pixijs = Js.Unsafe.Import.(static (star ~import_as:"PIXI" ()) "pixi.js")

let _ =
    let f = fun%js.async () ->
        let app = new%js pixi._Application in
        let%js.await _ = app#init({ background = Js.string "#1099bb"; resizeTo = Js.Unsafe.global }) in
        Js.Unsafe.global##.document##.body##appendChild app.canvas
        (* ... snip ... see above link *)
        app.ticker#add
            (* this annotation makes sure the fun is in a non-curried js-style.  Not sure if such a feature already exists *) 
            (fun%js time ->
                container.rotation <- container.rotation -. 0.01 *. time##.deltaTime)
    in
        Js.Async.no_wait f () (* For the async IIFE *)

Looking forward to hearing your criticisms; I have found using js_of_ocaml incredible and pleasant for working with ocaml code, but somewhat less pleasant when trying to make/use bindings and extant js libraries from ocaml. I'd like to help bridge the gap!

Fizzixnerd commented 4 months ago

ping @hhugo ; if this isn't a good time just let me know and I'll direct my efforts elsewhere ^_^

hhugo commented 4 months ago

I thinks there is a misunderstanding here. This issue was about being able to import/export inside js stubs (e.g. in https://github.com/ocsigen/js_of_ocaml/blob/master/runtime/compare.js). In particular, using export instead of using magic comment such as //Provides: ***

You seem to be interested in using import on the OCaml side.

Fizzixnerd commented 4 months ago

Will open new issues, thanks for the clarification.