Closed jchavarri closed 2 years ago
@jchavarri and I had a conversation about this on Discord and thought maybe a good start would be a syntax that looks something like this:
module MyReactComponent = struct
external%component make: name: Js.js_string Js.t -> React.element =
{|require("my-react-library").MyReactComponent|}
end
That should generate something that looks like this:
module MyReactComponent = struct
type makeProps
let makeProps ~(name: Js.js_string Js.t) () : makeProps =
Js_of_ocaml.Js.Unsafe.obj [| ("name", name); |]
let make : makeProps React.component =
Js_of_ocaml.Js.Unsafe.js_expr {|require("my-react-library").MyReactComponent|}
end
@zbaylin thanks for the write up! I love the idea of using the external string payload to pass the call to require
✨
Just some additional context about the transformation. After introducing first-class support for OCaml syntax we moved the createElement
call inside the component definitions, so we can create new elements from OCaml by just doing Foo.make
(no need for ppx on the element creation side, which is more ergonomic and easier to maintain).
To preserve this behavior, we should do the same for externals.
Including additional things like key
etc, the full transformation could look like:
module MyReactComponent = struct
let make =
let make_props :
name:'name
-> ?key:string
-> unit
-> < name: 'name Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t =
fun ~name ?key () ->
let open Js_of_ocaml.Js.Unsafe in
obj
[| ( "key"
, inject
(Js_of_ocaml.Js.Optdef.option
(Option.map Js_of_ocaml.Js.string key) ) )
; ("name", inject name) |]
in
let make
(props : < name: 'name Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t)
=
let f :
< name: 'name Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t
-> React.element =
Js_of_ocaml.Js.Unsafe.js_expr
{|require("my-react-library").MyReactComponent|}
in
f props
in
fun ~name ?key () -> React.createElement make (make_props ?key ~name ())
end
The good news is that the ppx already has functions to generate most of this code, the only thing that would need changing is the body of the inner make
:
make_props
can be generated by fully reusing make_make_props
:
https://github.com/ml-in-barcelona/jsoo-react/blob/b20594789b7edf63ec73ec1aac2f3d7d5e7b76ee/ppx/ppx.ml#L571-L572make
function can reuse parts of make_js_comp
(params annotation, createElement
call):
https://github.com/ml-in-barcelona/jsoo-react/blob/b20594789b7edf63ec73ec1aac2f3d7d5e7b76ee/ppx/ppx.ml#L728-L729One possible path to implement this could be to start by parsing the external
AST node + payload, and from there see which pieces of AST data become unavailable (compiler will complain). For example, the following function make_ml_comp
does not make sense under the external
context because we have no component implementation in this case:
https://github.com/ml-in-barcelona/jsoo-react/blob/b20594789b7edf63ec73ec1aac2f3d7d5e7b76ee/ppx/ppx.ml#L795-L798
Another thing is that in terms of workflow, it's very useful to me to work with the ppx test setup that is available, I usually go to input_ocaml.ml
, write some component or element code, run make test
, and make sure the output is as I would expect (or works backwards until I fix it).
I hope all this makes sense, if there are questions please lmk! :)
Awesome @jchavarri -- that makes a ton of sense! I'll look into that last part 👍
@jchavarri the only thing I'm worried about regarding calling require
directly is apparently webpack doesn't de-dupe by default: https://stackoverflow.com/questions/25509471/webpack-multiple-requires-resolving-to-same-file-but-being-imported-twice
Maybe a first step is to just use that de-duping optimizer, but I think we should try to think of a way to de-dupe in the bindings themselves rather than externally
the only thing I'm worried about regarding calling require directly is apparently webpack doesn't de-dupe by default
Hm I'm not sure. That would be quite unexpected, as regular apps call require("react")
multiple times across different modules. Here's a related answer: https://stackoverflow.com/a/33314481/617787
the only thing I'm worried about regarding calling require directly is apparently webpack doesn't de-dupe by default
Hm I'm not sure. That would be quite unexpected, as regular apps call
require("react")
multiple times across different modules. Here's a related answer: https://stackoverflow.com/a/33314481/617787
Huh, I must have misunderstood the original answer. That makes our lives a lot easier 😅
One missing part is binding to JavaScript components. Right now there are some examples in
core.mli
(e.g. toFragment
) using gen_js_api, but they are written manually, and use some magic conversions due to gen_js_api inability to parse some types like< children: element Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t
.It'd be nice to have some solution that decorates externals (or some other type of node) to bind to these JS components, but some questions quickly arise:
jsoo_global_object
, like it's done in example.