package html_of_jsx

  1. Overview
  2. Docs

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

  • Minimal design. It should just get out of the way. The ppx is seamless and the interface only exposes 5 functions and 2 types.
  • Type-safe validates correct attributes and its types
  • Works with OCaml, Reason and mlx
  • Brings the "component model" to HTML
  • Supports all of Reason's JSX features (uppercase components, fragments, optional attributes, punning)
  • but with a few improvements (lowercase components, no need for a ppx annotation)
  • No React idioms (no className, no htmlFor, no onChange, etc...), just plain HTML5
  • Integrates well with Htmx and others (see the htmx page)
  • Designed to work on the server, but can be used on the client-side (with Melange or jsoo)

API

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

  • A way to create DOM elements and nodes (JSX.string, JSX.int, JSX.null, JSX.list, JSX.unsafe) into a JSX.t tree
  • and JSX.render wich transforms a JSX.t tree into an HTML string
  • the rest are your functions that return JSX.t trees, which are known as "components"

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" />

Extra attributes

You can enable extra attributes in the ppx, and we currently support: htmx and react, check the pages for more information: htmx and react.

OCaml

Innovation. Community. Security.