Why Bonsai?
The successor to Incr_dom, Bonsai is a front-end web framework following the Elm style of a functional user-interface API.
Composition
UI Components are implemented as purely functional state-machines, and are composible in multiple ways:
- Parallel: Components can be built out of sub-components, and these sub-components don’t know about one another.
- Serial: Components can compute values that are used downstream by other components.
Incremental
Incrementalization inside the framework means that values don’t get recomputed until necessary. This applies to every value, not just the view.
open! Core
open Bonsai_web
open Bonsai.Let_syntax
(* An example of a small Bonsai component for creating counter
widgets ("+" and "-" buttons allow you to increment and decrement a
number). The state of each counter is housed in a shared [Map]. Adding
a new counter is as simple as adding a value to the map. *)
let component graph =
let state, inject =
Bonsai.state_machine
~default_model:Int.Map.empty
~apply_action:(fun _ctx model -> function
| `New_counter -> Map.add_exn model ~key:(Map.length model) ~data:0
| `Incr_by (counter_id, diff) ->
Map.update model counter_id ~f:(function
| None -> diff
| Some x -> x + diff))
graph
in
let%arr state and inject in
let button text action =
Vdom.Node.button
~attrs:[ Vdom.Attr.on_click (fun _ -> inject action) ]
[ Vdom.Node.text text ]
in
let add_button = button "add" `New_counter in
let for_each (i, c) =
Vdom.Node.div
[ button "-1" (`Incr_by (i, -1))
; Vdom.Node.textf "%d" c
; button "+1" (`Incr_by (i, 1))
]
in
let counters = state |> Map.to_alist |> List.map ~f:for_each in
Vdom.Node.div (add_button :: counters)
;;
let () = Bonsai_web.Start.start component
Learn More
Sources
If you want to understand the design of the Bonsai API, it might be helpful to read this paper on the arrow calculus by Lindley et al.