html_of_jsx Render HTML with JSX

html_of_jsx is a library and a ppx to write HTML declaratively in OCaml, Reason and mlx.

This library was extracted from server-reason-react and simplified to only work with HTML and SVG.

Installation

opam install html_of_jsx

add it to your dune file

(libraries html_of_jsx)
(preprocess (pps html_of_jsx.ppx))

Overview

API

The library exposes a single module JSX with a minimal API:

Visit the JSX page for the complete interface documentation

It's just HTML

We stick to the HTML standard, so no react idioms like className, no htmlFor, etc.

let element = <span> {JSX.string("Hello world!")} </span>;

Note: reserved keywords aren't possible as props. For example: class => class_ or type => type_.

Components are functions with labeled arguments

let component = (~name, ()) => {
  <div> <h1> {JSX.string("Hello, " ++ name ++ "!")} </h1> </div>;
};

JSX.render(<component name="lola" />);

> Note that the component function needs to have a last argument of type unit in order to work properly with labelled arguments. Explained on the OCaml manual: Functions with only labelled arguments, need a last non labelled argument to be able to be called as a non curried function.

children: elements can be nested in other components

This makes possible to compose any HTML and abstract those components away. ~children is a prop that recieves the nested elements from the component invocation.

let hero = (~children, ()) => {
  <main class_="fancy-hero"> {children} </main>;
};

JSX.render(<hero> {JSX.string("Hello, world!")} </hero>);

In this example, the hero component is a function that takes a children prop and returns a main element with the class fancy-hero. The children prop is the nested elements from the component invocation.

Uppercase components default to the make function

module Button = {
  let make = () => {
    <button onclick="onClickHandler"> {JSX.string("Click me")} </button>;
  };
};

JSX.render(<Button />);
// is equivalent to
JSX.render(<Button.make />);

Brings the power of interleaving expressions within your JSX

let component = (~name, ~children, ()) => {
  <div>
    <h1> {("Hello, " ++ name ++ "!") |> JSX.string} </h1>
    <h2> children </h2>
  </div>;
};

JSX.render(
  <component name="World"> {JSX.string("This is a children!")} </component>,
);

List of childrens are available with JSX.list

A component can receive more than one children, which is the case for the ul element, but many other elements too. To make sure a list of elements is an element, use the JSX.list function.

JSX.render(
  <ul>
    {["This", "is", "an", "unordered", "list"]
     |> List.map(item => <li> {JSX.string(item)} </li>)
     |> JSX.list}
  </ul>,
);

Supports list of elements as children

There are other cases where you want to construct many JSX.element without wrapping them in a HTML element, that's what Fragment is for: <> ... </>.

let component: JSX.element =
  <>
    <div class_="md:w-1/3" />
    <div class_="md:w-2/3" />
  </>;

let output = JSX.render(<component/>);
/* <div class="md:w-1/3"></div><div class="md:w-2/3"></div> */

Type-safe

HTML attributes are type-checked and only valid attributes are allowed. It also ensures that the value is correct.

    <h1 noop=1> {JSX.string("Hello, world!")} </h1>
    ^^^
// Error: prop 'noop' is not valid on a 'h1' element.
    <h1 class_=1> {JSX.string("Hello, world!")} </h1>
               ^
// Error: This expression has type int but an expression was expected of type string

And also in case of a misspell, it recommends the closest attribute


          <div ?onClick />
          ^^^^
// Error: prop 'onClick' is not valid on a 'div' element.
//        Hint: Maybe you mean 'onclick'?

Works with Reason

let component = (~name, ()) => {
  <div>
    <h1> {JSX.string("Hello, " ++ name ++ "!")} </h1>
  </div>;
};

JSX.render(<component name="World" />);

Works with mlx

mlx is an OCaml syntax dialect which adds JSX syntax expressions

let component ~name () =
  <div>
    <h1> ("Hello, " ^ name ^ "!") </h1>
  </div>

JSX.render <component name="World" />

ppx flags

The ppx supports several flags to customize its behavior:

-htmx

Enables htmx attributes in HTML and SVG elements. See the htmx page for more information.

(preprocess (pps html_of_jsx.ppx -htmx))

-react

Enables React-specific attributes (like className, htmlFor, etc.) in HTML and SVG elements. See the react page for more information.

(preprocess (pps html_of_jsx.ppx -react))

-disable-static-opt

Disables static HTML optimization. By default, the ppx pre-renders elements with static content at compile time for better performance. This flag forces all elements to use JSX.node at runtime instead of pre-rendered HTML.

(preprocess (pps html_of_jsx.ppx -disable-static-opt))

This flag may be useful for:

Example:

(* With static optimization (default): *)
let element = <div class_="container"></div>;
(* Compiles to: JSX.unsafe {|<div class="container"></div>|} *)

(* With -disable-static-opt: *)
let element = <div class_="container"></div>;
(* Compiles to: JSX.node("div", [("class", `String "container")], []) *)