Vanilla JSX in Reason


#1

The docs for JSX in Reason are incomplete. How do I use plain JSX in plain Reason, without any React externals. For example, simply dropping JSX into some Reason causes unfound module errors.


#2

The JSX transformation is explained here: https://reasonml.github.io/reason-react/docs/en/jsx

The sugared JSX version turns into unsugared function calls, referring to the modules React and ReactDOMRe. If you want to use it in your own projects, an easy way to do that is to create modules and functions with those exact names which do what you want them to.


#3

My confusion is stemming from the statement in the Reason docs that Reason JSX is not Reason-React, specifically stating that the JSX is independent of any specific framework, and yet everything I find refers to React. Can I use JSX only with a framework?


#4

As far as I know you can use JSX with any library or framework as long as provide the appropriate modules and functions that JSX desugars to, as I mentioned before.

For example,

<div foo={bar}> {child1} {child2} </div>

desugars to

ReactDOMRe.createDOMElementVariadic(
  "div",
  ~props=ReactDOMRe.domProps(~foo=bar, ()),
  [|child1, child2|]
);

So as long as you create a module ReactDOMRe with a function createDOMElementVariadic, the desugar will work fine and therefore JSX will work fine. Now what your version of ReactDOMRe.createDOMElementVariadic does, is upto you. That’s how you can integrate JSX with any library or framework.


#5

This is incorrect. What described here is ReasonReact’s JSX transformation, reason’s JSX transformation is difference. I’m on mobile right now so I can’t provide a full answer, But I’ll try to remember and edit this reply later when I got back to my PC


#6

I was just hoping to get some elaboration on this.


#7

Can I use JSX only with a framework?

You can use JSX in non-reasonreact framework. More details here - https://reasonml.github.io/docs/en/jsx.

Vanilla JSX is transformed by a compiler into normal function calls, for example,
<div foo={bar}> child1 child2 </div>

is tranformed into

([@JSX] div(~foo=bar, ~children=[child1, child2], ()));

which is a normal function call syntax in Reason. So if you want to use the above JSX in your normal reasonml native program, you will need to define a function called div, like so

let div = (~firstname, ~lastname, ~children as _) => Printf.sprintf("Hello %s, %s!", lastname, firstname);
let () = print_string(<div firstname="foo" lastname="bar"/>);

The [@JSX] is an annotation called attribute in ocaml/reasonml. More details here (https://caml.inria.fr/pub/docs/manual-ocaml/manual035.html). This attribute([@JSX]) is used by ReasonReact ppx(https://ocamlverse.github.io/content/ppx.html) to further transform function call syntax to reasonreact specific forms, i.e. from

<div className="hello" id="id1"/>;

to

((div ~first:"" ~className:(("hello")[@reason.raw_literal "hello"])
      ~id:(("id1")[@reason.raw_literal "id1"]) ~children:[] ())[@JSX ])

is done by the ReasonML compiler.
From

((div ~first:"" ~className:(("hello")[@reason.raw_literal "hello"])
      ~id:(("id1")[@reason.raw_literal "id1"]) ~children:[] ())[@JSX ])

to

ReactDOMRe.createDOMElementVariadic(
  "div",
  ~props=ReactDOMRe.domProps(~className="hello", ~id="id1" ()),
  [||]
);

is done by ReasonReact ppx(https://github.com/BuckleScript/bucklescript/blob/72c23b3378afe9cd8477e9a16fcae958039f9828/jscomp/syntax/reactjs_jsx_ppx.cppo.ml)
Finally, the javascript output from the last transformation is done by the bucklescript compiler.

// Generated by BUCKLESCRIPT VERSION 5.0.4, PLEASE EDIT WITH CARE
'use strict';

var React = require("react");

React.createElement("div", {
      className: "hello",
      id: "id1"
    });

/*  Not a pure module */

If you want to explore further on the JSX ppx mechanism, you can see my experiment here -https://github.com/bikallem/jsx-ssr/. Here I have transformed normal ReasonML JSX to render HTML in the server side without the use of nodejs.


#8

This goes quite a ways towards explaining how things should work in one particular way and I appreciate it very much. I’m still confused because if I open up a Reason file and just paste, for example from the docs, this

let component = <div foo={bar}> child1 child2 </div>;

I get this error: Error: Unbound module ReactDOMRe

This means that, without any other input, the compiler is assuming something related to React. Is that correct?


#9

I think it is necessary to set some (which ?) options in bsconfig.json


#10

@maarekj is right, the behavior of JSX is related to your bsconfig.json. Specifically, if your bsconfig has:

{
  ...
  "reason": {
    "react-jsx": 2 // or 3
  }
}

then the ReasonReact-specific version of JSX will be used.


#11

Thanks for the clarity. I was hoping to specifically avoid ReasonReact and instead deal with just Reason. It was difficult since everyone writing online is only talking about ReasonReact. I can appreciate why–it’s marketable–but for someone with eyes only on the language itself, it was frustrating.


#12

If you want to use ReasonML/JSX without ReasonReact then Reason native is what you want.

To get started with ReasonML native, you might find these projects useful
https://github.com/esy/esy - a native/opam compatible package manager
https://github.com/ocaml/dune - an ocaml/reasonml build tool

https://github.com/esy/pesy - an overlay over esy tool. For beginners of esy.

Note: If you specifically want to use JSX with reason native, see my first posts on this thread. An example of using JSX in native setting is https://github.com/revery-ui/revery where JSX is used to create a view-engine like for native desktop applications. Revery is in turn used to create a fast vim-powered editor called onivim2 - https://github.com/onivim/oni2