Skip to content

Other authoring layers

Template components are the authoring form that renders on the server and hydrates. Two other layers build DOM in the browser: authored views, which construct each instance's DOM during mounting, and Fable.Ranvier, a small library of DOM helpers.

Authored views

An authored view is a Program.View: a function over the mount's backend. Generated.Html.Program holds a constructor for each of the 112 HTML elements, Generated.Html.Attrs and the per-element modules hold modifiers, and Ranvier.View.Dom.Authoring holds text, components, keyed arrays, branches and boundaries.

open global.Ranvier
open Ranvier.View
open Ranvier.View.Dom.Authoring

module H = Ranvier.View.Dom.Generated.Html
module Html = Ranvier.View.Dom.Generated.Html.Program
module Attr = Ranvier.View.Dom.Generated.Html.Attrs

let rowsView (rows: Signal<(int * string) array>) : Program.View =
    Html.section
        [| Attr.classStatic "rows" |]
        [|
            Html.button
                [|
                    H.Button.onClick (fun _ ->
                        let next = rows.Value.Length + 1
                        rows.Value <- Array.append rows.Value [| next, "Item " + string next |])
                |]
                [| text "Add row" |]
            keyedArray fst (fun () -> rows.Value) (fun current ->
                Html.div [| Attr.idStatic ("row-" + string (fst (current ()))) |] [| reactiveText (fun () -> snd (current ())) |])
        |]
FunctionMeaning
text value, reactiveText readFixed and tracked text.
fragment childrenChildren in order.
createComponent setup propsA component whose setup runs once per mounted instance.
keyedArray key read renderKeyed rows; each row's render receives a reader of its current item.
branch key read renderOne instance, kept while its key is equal under =.
boundary pending failed contentA boundary over authored views.

Ranvier.View.Dom.Generated.Html requires qualified access; alias it as above. Generated.Html.Authoring adds overloaded Attrs and Props modules for the authored layer, whose members accept a value, a unit -> 'T reader, a Signal<'T> or a Memo<'T>. Keep that open out of files that open the sugar.

Run an authored view in a mount with Program.run view context.Backend, the same call that runs Component.render. A static snapshot comes from Representation.Html.render, written in full beside the Html alias:

let snapshot (rows: Signal<(int * string) array>) : string =
    Ranvier.View.Representation.Html.render Omit (Program.run (rowsView rows))
<section class="rows"><button>Add row</button><div id="row-1">Alpha</div></section>

Authored views inside templates

A template embeds a Program.View through a view hole, t.view:

open Ranvier.View
open Ranvier.View.Sugar

type Card = { Caption: string; Body: Program.View }

let card =
    Component.define (fun _ (card: Card) -> card) (fun t ->
        div (Attrs.className "card") {
            h3 () { fun c -> c.Caption }
            t.view (fun c -> c.Body)
        })

The reader runs once per instance, untracked. The server writes the view between region markers; hydration mounts a view hole fresh between those markers rather than adopting its nodes, and an authored component's CreateId issues ids that can differ between server and client.

Fable.Ranvier

src/Fable.Ranvier is a proof-of-concept library of DOM helpers. Nodes are created with ordinary F# functions, and each binding is one Ranvier effect:

open Fable.Core.TS.Dom
open global.Ranvier
open Fable.Ranvier

let graph = new Graph ()
let count = graph.Run (fun () -> createSignal 0)
let host = Global.document.getElementById("app").Value

let mounted =
    Mount.mount graph host (fun () ->
        Dom.element "button"
            [ Dom.on "click" (fun _ -> count.Value <- count.Value + 1) ]
            [ Dom.reactiveText (fun () -> $"Count: {count.Value}") ]
        :> Node)
  • Mount.mount graph host factory appends the factory's single root node and returns an IDisposable that removes the node and stops its bindings and listeners.
  • Event handlers are batched by default: the DOM updates once when the handler's batch finishes.
  • Mount.mountWith { DomOptions.Default with Scheduling = Microtask } graph host factory also defers DOM writes to a microtask.

The library has static child structure only: lists, conditional content, server rendering and hydration belong to template components. Its README documents each helper and the scheduling options.

Edit this page