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'?
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" />
You can enable extra attributes in the ppx, and we currently support: htmx and react, check the pages for more information: htmx
and react
.