Skip to content
Meltea

Get started

Brew your first program.

You need OCaml ≥ 5.4, Melange ≥ 5.1 and dune ≥ 3.16, and Node for the tests. Five steps from an empty glass to a counter that writes one text node per click.
  1. Step one

    Pin the packages.

    Until the opam release, pin melhole, meltea and meltea-ppx from the repository.

    shell
    opam pin add melhole meltea meltea-ppx https://github.com/jongleb/meltea.git
  2. Step two

    Add it to a dune file.

    A melange.emit stanza with the library and the ppx:

    dune
    (melange.emit
     (target app)
     (libraries meltea)
     (preprocess (pps melange.ppx meltea-ppx)))

    and (dialect (name mlx) …) in dune-project for .mlx files. The repository's own dune-projectdeclares it like this:

    dune-project
    (dialect
     (name mlx)
     (implementation
      (extension mlx)
      (merlin_reader mlx)
      (preprocess
       (run mlx-pp %{input-file}))))
  3. Step three

    Write a model, an update and a view.

    onClick=Increment sends a message. Program.sandbox runs the program on a root element.

    counter.mlx
    type message = Increment | Decrement | Reset
    
    let update message count =
      match message with Increment -> count + 1 | Decrement -> count - 1 | Reset -> 0
    
    let view count =
      <div className="counter">
        <button onClick=Decrement>(Html.text "-")</button>
        <output>(Html.int count)</output>
        <button onClick=Increment>(Html.text "+")</button>
        <button onClick=Reset disabled=(count = 0)>(Html.text "reset")</button>
      </div>
    
    let () = Option.iter (fun root -> ignore (Program.sandbox ~init:0 ~update ~view root)) root
  4. Step four

    Build, test and serve it.

    In the repository, the examples run under Vite; its config resolves the bare imports Melange emits inside each _build/…/outputdirectory. With ~start:Program.hot, an edit shows the new view of the old model.

    shell
    $ dune build && dune test && dune build @runtest-js && npx vitest run
    shell
    $ npx vite                            # then open http://localhost:5173/examples/counter/
  5. Step five

    Serve it from a server, too.

    One module with the model, update and view builds for both sides: natively for the server againstmeltea.server, and for the browser with the hydration code compiled in.

    dune
    (executable                              ; the server, native
     (name server)
     (libraries meltea.server)
     (preprocess (pps meltea-ppx -server)))
    
    (melange.emit                            ; the browser
     (target app)
     (libraries meltea)
     (preprocess (pps melange.ppx meltea-ppx -hydratable)))
    server.ml · main.mlx
    (* server: the capture script, the model as data, the page *)
    "<script>" ^ Melhole_server.capture ^ "</script>" ^ Melhole_server.data ~id:"model" (encode model)
    ^ "<div id=\"root\">" ^ Program.render ~view:Page.view model ^ "</div>"
    
    (* browser: start from the server's model, no init and no commands run twice *)
    Option.iter
      (fun text -> ignore (Program.sandbox ~start:Program.hydrate ~init:(decode text) ~update:Page.update ~view:Page.view root))
      (Melhole.Served.data ~id:"model")
    One Meltea module, page.mlx, built twice. meltea-ppx for the browser compiles the view into a template parsed once and cloned, a walk to the output and a refresh that writes the text only when count changed. meltea-ppx -server compiles the same view into code that writes strings with no DOM, natively or in Node: the static markup joined into a few strings at compile time and one call per dynamic part, the class and the count. The server writes HTML in React's format, the same bytes natively and under Melange, and the browser build hydrates it

Afiyet olsun.

From here, the guide walks from the counter to TodoMVC: commands, subscriptions, lists, routing and transitions.