ocaml-mlx / mlx

OCaml .mlx syntax dialect which adds JSX syntax expressions
Other
104 stars 2 forks source link

Support all HTML attribute syntax #4

Open davesnx opened 10 months ago

davesnx commented 10 months ago

Currently Reason JSX is limited to React's JSX, which is ok for newcomers and React devs but very undesired for other libs (Htmx, svelte, solidjs, vue, etc) but also for HTML directly.

Being able to support entirely the HTML spec would be a big win for mlx in this case. The reasonings are beyond libraries, and also integrations with DevTools, copy/paste and learnability.

I'm not sure if it's remotely possible to support some of these features, but having the way to achieve it, it's a must for me. The current situation with Reason JSX is to not be able to use data-* attributes directly, and need to React.createElement/cloneElement.

leostera commented 7 months ago

I second this, being able to write vanilla HTML is super important for lowering friction on tools like Tailwind UI that give you large templates.

I think we can start with some heuristics to turn these into valid OCaml function parameters. For example data-id could become ~data_dash_id and x:html could become ~x_colon_html.

These aren't ideal, but if we're consistent then these would be learnable , and at least the name is verbose enough to tell you where they come from.

rizo commented 3 months ago

One additional aspect that I think is often overlooked is the ability abstract over attributes. In the current mlx version it is possible to abstract over elements, but attributes are always mapped to labels, which makes them fixed.

Consider the following signature that models HTML:

module Html : sig
  type elem

  val elem : tag:string -> attr list -> elem list -> elem 
  val div : attr list -> elem list -> elem
  val text : string -> elem
  (* ... *)

  type attr

  val attr : string -> string -> attr
  val href : string -> attr
  val class_name : string -> attr
  (* ... *)
end

This is very similar to how HTML is described in Elm.

One advantage of this approach is that it is possible to refactor attributes:

let html =
  let open Html in
  let attr = href "#" in
  let data = text "Hello"
  div [attr; class_name "foo"] [
    data;
  ]

In my opinion, instead of trying to map attributes to labels, which require the attribute to be fixed, it is better to aim for something more generic.

See the examples below for some suggested translations:

<div />
// Mlx.Elem.div [] []

<Foo />
// Foo.make [] []

<Foo.el />
// Foo.el [] []

<div> {v} </div>
// Mlx.Elem.div [] [ v ]

<div> {v1} {v2} </div>
// Mlx.Elem.div [] [ v1; v2 ]

<div> {42} </div>
<div> {"hello"} </div>
// Mlx.Elem.div [] [ Mlx.int 42 ]
// Mlx.Elem.div [] [ Mlx.string "hello" ]

<div a="hello" />
// Mlx.Elem.div [Mlx.Attr.a (Some "hello")] []

<div b />
// Mlx.Elem.div [Mlx.Attr.b None] []

<div data-a data-b="x" />
// Mlx.Elem.div [Mlx.Attr.data "a" None; Mlx.Attr.data "b" (Some "x")] []

<div {my_attr} />
// Mlx.Elem.div [my_attr None] []

<div {Foo.my_attr=42} />
// Mlx.Elem.div [Foo.my_attr (Some 42)] []

Of course, the generated nodes should still be annotated with a ppx attribute in practice.

My main message is that, I think it's as important to abstract over attributes as it is over elements.