html_of_jsx
html_of_jsx is an implementation of JSX designed to render HTML on the server, without React or anything else. It's a simple library that allows you to write HTML in a declarative way with the component model.
This library is extracted from server-reason-react and simplified to just work with HTML5.
Installation
opan pin add html_of_jsx "https://github.com/davesnx/html_of_jsx"
(libraries html_of_jsx.lib)
(preprocess (pps html_of_jsx.ppx))
API
Jsx
module that exposes some helpers to construct elements and Html_of_jsx
to render them, the rest are functions with labelled arguments.
Features
It's just HTML (no className
, no htmlFor
, etc.)
let element = <a href="https://x.com/davesnx">
<span> {"Click me!"} </span>
</a>
> 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.text("Hello, " ++ name ++ "!")} </h1> </div>;
};
Html_of_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.
Uppercase components default to make
module Button = {
let make = () => {
<button onclick="onClickHandler"> {Jsx.text("Click me")} </button>;
};
};
Html_of_jsx.render(<Button />);
// is equivalent to
Html_of_jsx.render(<Button.make />);
Brings the power of interleaving expressions (stolen from JSX)
let component = (~name, ~children, ()) => {
<div>
<h1> {("Hello, " ++ name ++ "!") |> Jsx.text} </h1>
<h2> children </h2>
</div>;
};
Html_of_jsx.render(
<component name="World"> {Jsx.text("This is a children!")} </component>,
);
List of childrens are available with Jsx.list
Html_of_jsx.render(
<ul>
{["This", "is", "an", "unordered", "list"]
|> List.map(item => <li> {Jsx.text(item)} </li>)
|> Jsx.list}
</ul>,
);
Type-safe
HTML attributes are type-checked and only valid attributes are allowed
<h1 noop=1> {Jsx.text("Hello, world!")} </h1>
^^^
// Error: prop 'noop' isn't valid on a 'h1' element.
<h1 class_=1> {Jsx.text("Hello, world!")} </h1>
^
// Error: This expression has type int but an expression was expected of type string
And also friendly, it recommends you the correct attribute if you misspell it
<div ?onClick />
^^^^
// Error: prop 'onClick' isn't valid on a 'div' element.
// Hint: Maybe you mean 'onclick'?
Minimalistic
Only 2 function to learn, the rest are your functions (aka components):
Html_of_jsx.render
to render your HTMLJsx.text
to inline text
The rest are helpers on Jsx.*
, like (Jsx.int
). Check the !Jsx
if you are curious
Html_of_jsx.render(<h1>{Jsx.text("Hello, world!")}</h1>);
Supports children as list of elements
let component = (~name, ~children, ()) => {
<div>
<h1> {Jsx.text("Hello, " ++ name ++ "!")} </h1>
<h2> {children} </h2>
</div>
};
Html_of_jsx.render(<component> {"This is a children!"} </component>)
Supports fragments
let component: Jsx.element = <> <div class_="md:w-1/3" /> <div class_="md:w-2/3" /> </>;
Html_of_jsx.render(<component> {"This is a children!"} </component>)
let component = (~name, ()) => {
<div> <h1> {Jsx.text("Hello, " ++ name ++ "!")} </h1> </div>;
};
Html_of_jsx.render(<component name="World" />);
Works with mlx (https://github.com/andreypopp/mlx)
let component ~name =
<div>
<h1> ("Hello, " ^ name ^ "!") </h1>
</div>
Html_of_jsx.render <component name="World" />