Skip to content

Commit ae24bb7

Browse files
authored
Add a basic guide (#1)
1 parent 9ac368e commit ae24bb7

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed

GUIDE.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Usage Guide
2+
3+
## Overview
4+
5+
`Halogen.VDom` is built on [Mealy machines](https://en.wikipedia.org/wiki/Mealy_machine).
6+
Given an input `VDom`, the machine yields a `Node`, the next machine step, and a finalizer.
7+
8+
```purescript
9+
import Halogen.VDom as VDom
10+
11+
type MyVDom = VDom.VDom ...
12+
13+
render ∷ MyState → MyVDom
14+
15+
main = do
16+
-- Build the initial machine
17+
machine1 ← V.buildVDom myVDomSpec (render state1)
18+
19+
-- Attach the output node to the DOM
20+
appendChildToBody (V.extract machine1)
21+
22+
-- Patch
23+
machine2 ← V.step machine1 (render state2)
24+
machine3 ← V.step machine2 (render state3)
25+
...
26+
```
27+
28+
Out of the box, only very basic text and node creation is supported. Attributes,
29+
properties, event listeners, hooks, etc. are left for library implementors to
30+
plug in as needed. Library authors should likely `newtype` their wrappers to
31+
get more convenient instances (eg. `map`ping over inputs from event listeners).
32+
33+
## Extending
34+
35+
The core `VDom a w` type is parameterized by the types for element attributes
36+
and custom widgets. Element attributes will likely be a sum for the usual suspects
37+
(DOM attributes, properties, event listeners, lifecycle hooks) and mutate a
38+
given DOM `Element`, while widgets give you complete control over the patching
39+
and diffing of a tree (eg. thunks, custom components, etc).
40+
41+
When you start your initial machine, you provide a `VDomSpec`, which contains
42+
the machines for running your attributes and widgets.
43+
44+
```purescript
45+
import Halogen.VDom as VDom
46+
47+
data MyAttribute
48+
data MyWidget
49+
50+
makeSpec ∷ ∀ eff. DOM.Document → VDom.VDomSpec eff MyAttribute MyWidget
51+
makeSpec document =
52+
VDom.VDomSpec
53+
{ buildWidget: ...
54+
, buildAttributes: ...
55+
, document
56+
}
57+
```
58+
59+
The type signature for `buildWidget` looks like:
60+
61+
```purescript
62+
buildWidget
63+
∷ ∀ eff a
64+
. V.VDomSpec eff a MyWidget
65+
→ V.VDomMachine eff MyWidget DOM.Node
66+
```
67+
68+
`buildWidget` takes a circular reference to the `VDomSpec` you are building so you
69+
can have recursive trees. The core though is in the returned `VDomMachine` which
70+
takes your widget type, and yields a DOM node.
71+
72+
The type signature for `buildAttributes` looks like:
73+
74+
```purescript
75+
buildAttributes
76+
∷ ∀ eff
77+
. DOM.Element
78+
→ V.VDomMachine eff MyAttribute Unit
79+
```
80+
81+
This takes the current `Element` and yields a machine which takes your attribute
82+
type and yields `Unit`.
83+
84+
If you don't have any custom widgets, you can supply a `Void` machine.
85+
86+
```purescript
87+
import Halogen.VDom as VDom
88+
import Halogen.VDom.Machine as Machine
89+
90+
data MyAttribute
91+
92+
makeSpec ∷ ∀ eff. DOM.Document → VDom.VDomSpec eff MyAttribute Void
93+
makeSpec document =
94+
VDom.VDomSpec
95+
{ buildWidget: const (Machine.never)
96+
, buildAttributes: ...
97+
, document
98+
}
99+
```
100+
101+
## Creating Machines
102+
103+
A `Machine`'s type looks like:
104+
105+
```purescript
106+
type Machine m a b = a → m (Step m a b)
107+
108+
data Step m a b = Step b (Machine m a b) (m Unit)
109+
```
110+
111+
So it is just an effectful function from some input to a `Step`, which is a
112+
product of an output value `b`, the next transition, and a finalizer. Finalizers
113+
are useful when your widgets or attributes need to perform cleanup.
114+
115+
The structure of a widget machine will likely follow this pattern:
116+
117+
```purescript
118+
import Halogen.VDom as V
119+
120+
createWidgetNode ∷ MyWidget → V.VDomEff eff DOM.Node
121+
122+
patchWidgetNode ∷ DOM.Node → MyWidget → MyWidget → V.VDomEff eff DOM.Node
123+
124+
cleanupWidgetNode ∷ DOM.Node → MyWidget → V.VDomEff eff Unit
125+
126+
buildWidget
127+
∷ ∀ eff a
128+
. V.VDomSpec eff a MyWidget
129+
→ V.VDomMachine eff MyWidget DOM.Node
130+
buildWidget spec = render
131+
where
132+
render ∷ V.VDomMachine eff MyWidget DOM.Node
133+
render widget = do
134+
node ← createWidgetNode widget
135+
pure
136+
(V.Step node
137+
(patch node widget)
138+
(done node widget))
139+
140+
patch ∷ DOM.Node → MyWidget → V.VDomMachine eff MyWidget DOM.Node
141+
patch node1 widget1 widget2 = do
142+
node2 ← patchWidgetNode node widget1 widget2
143+
pure
144+
(V.Step node2
145+
(patch node2 myWidget2)
146+
(done node2 myWidget2))
147+
148+
done ∷ DOM.Node → MyWidget → V.VDomEff eff Unit
149+
done node widget = cleanupWidgetNode node widget
150+
```
151+
152+
Note that `Machine`s can keep any state they need to, it is just passed from
153+
machine to machine through closures.
154+
155+
The structure of an attribute machine will likely follow this pattern:
156+
157+
```purescript
158+
import Halogen.VDom as V
159+
160+
applyAttributes ∷ DOM.Element → MyAttribute → V.VDomEff eff Unit
161+
162+
patchAttributes ∷ DOM.Element → MyAttribute → MyAttribute → V.VDomEff eff Unit
163+
164+
cleanupAttributes ∷ DOM.Element → MyAttribute → V.VDomEff eff Unit
165+
166+
buildAttributes
167+
∷ ∀ eff a
168+
. DOM.Element
169+
→ V.VDomMachine eff MyAttribute Unit
170+
buildAttribute elem = apply
171+
where
172+
apply ∷ V.VDomMachine eff MyAttribute Unit
173+
apply attrs = do
174+
applyAttributes elem attrs
175+
pure
176+
(V.Step unit
177+
(patch attrs)
178+
(done attrs))
179+
180+
patch ∷ MyAttribute → V.VDomMachine eff MyAttribute Unit
181+
patch attrs1 attrs2 = do
182+
patchAttributes elem attrs1 attrs2
183+
pure
184+
(V.Step unit
185+
(patch attrs2)
186+
(done attrs2))
187+
188+
done ∷ MyAttribute → V.VDomEff eff Unit
189+
done attrs = cleanupAttribute elem attrs
190+
```
191+
192+
Note that the `Element` is provided on initialization, and there is no meaninful
193+
output type because it is only effectful.
194+
195+
## Getting Performance
196+
197+
The core of `Halogen.VDom` strives to be as fast as possible. It does this
198+
through pervasive use of monomorphic `Eff` do-blocks (which are optimized into
199+
imperative JavaScript) and `Data.Function.Uncurried` (which eliminates the
200+
overhead of currying). It also provides a few monomorphic utilities in
201+
`Halogen.VDom.Util` to help cut down on allocations. Additionally there are
202+
some general purposes utilities to help with faster diffing.

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@ It's goals being:
2020
Notably, `Halogen.VDom` is largely useless out of the box. It does not support
2121
attributes, properties, or event listeners. It is intended to be extended
2222
(and likely `newtype`d) by other frameworks to suit their needs.
23+
24+
---
25+
26+
* Read the [guide](./GUIDE.md).
27+
* See the [test example](./test/Main.purs).

0 commit comments

Comments
 (0)