Skip to content

Getting Started

SolidJS for modern OCaml.

solidtype is Solid for OCaml. A component is written in OCaml’s JSX, compiled by Melange, and reaches the browser as the code Solid’s own compiler emits for the same JSX: the same templates, the same runtime calls, in the same order. What OCaml adds is its types. A reactive read in the wrong place does not compile, and neither does a prop left out, an attribute an element does not take, or a match in a Switch that misses a case.

solid_counter.mlx
module S = Solidtype.Solid
module Jsx = Solidtype.Jsx
let make () =
let count, set_count = S.create_signal 0 in
<button
class_="button button--primary"
type_=`button
on_click=(fun _ -> S.update set_count succ)>
"Clicked " (fun () -> Jsx.int (S.get count)) " times"
</button>
Package What it is
solidtype Solid 1.9 bound whole: signals, computations, resources, stores, its components and its renderer, server rendering included
ppx_solidtype rewrites JSX written in .mlx files into calls Melange compiles
vite-plugin-solidtype turns those calls back into JSX, which Solid’s compiler compiles as it compiles TSX
solidtype-kobalte Kobalte 0.13 bound whole, generated from its own declarations
solidtype-heroui HeroUI’s design ported by hand onto Kobalte

OCaml over TypeScript says why a front end is better written in OCaml. How it works follows one component through every step. Melange and dune and Vite and Astro say how each tool is set up, and Conventions how a name in Solid’s documentation is written in OCaml. The Solid and Kobalte tabs are the bindings, and HeroUI the components built on them.

It needs opam, with a switch of OCaml 5.5 or later, and Bun 1.3 or later, which installs the JavaScript side and runs Vite 8.

The OCaml side, from Hypha’s opam repository:

Terminal window
opam repo add hypha https://github.com/hyphatech/opam-repository.git
opam install solidtype # and solidtype-kobalte for Kobalte

The JavaScript side, with Vite and the plugin, which is an asset of each GitHub release:

{
"type": "module",
"dependencies": {
"solid-js": "~1.9.17",
"vite": "^8.3.0",
"vite-plugin-solid": "^2.11.14",
"vite-plugin-solidtype": "https://github.com/hyphatech/solidtype/releases/download/0.3.0/vite-plugin-solidtype-0.3.0.tgz"
}
}

dune-project reads .mlx files, OCaml with JSX:

(lang dune 3.18)
(using melange 0.1)
(dialect
(name mlx)
(implementation
(extension mlx)
(merlin_reader mlx)
(preprocess (run mlx-pp %{input-file}))))

The components are a Melange library, emitted as .jsx modules, the files the plugin rewrites:

; src/dune
(library
(name app)
(modes melange)
(libraries solidtype melange.dom)
(preprocess (pps melange.ppx ppx_solidtype)))
; dune
(melange.emit
(target out)
(libraries app)
(module_systems (es6 jsx)))

The counter above is src/app.mlx. An entry module mounts it, src/main.ml:

external get_element_by_id : string -> Dom.element = "getElementById"
[@@mel.scope "document"]
let (_ : unit -> unit) = Solidtype.Web.render App.make (get_element_by_id "app")

The plugin runs before Solid’s:

vite.config.js
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";
import solidtype from "vite-plugin-solidtype";
export default defineConfig({ plugins: [solidtype(), solid()] });

The page loads Melange’s output:

<!doctype html>
<div id="app"></div>
<script type="module" src="/_build/default/out/src/main.jsx"></script>

Dune builds before Vite reads what it wrote:

Terminal window
bun install
opam exec -- dune build
bun --bun vite build