I would start reading from this file. The start function is what wires everything up. beginner is just a functions that transform trivial app configuration the the one that start expects.
Configuration passed to start is expected to contain flags (which some data that is just forwarded to) init function, update and view functions.
init supposed to construct initial state and request any kind of side effects (usually io task) to be performed to bootstrap. There for it returns [model, fx] pair.
Initial model then gets rendered via view(model, address). Where model is whatever the immediate state of the app is and address is a function that could be used to trigger more updates. Any user just passes action describing it to the address. In turn received action triggers update(model, action) that supposed to return [model, fx] pair like init did. View itself returns virtual-dom describing the application UI.
One semi-confusing thing is that functions used to build virtual-dom tree (see dom.js) provide implementations of node, text and thunk that either just delegate to same named functions on mysterious driver or just return boxes that do that on .force(). This is to facilitate swappable drivers for rendering. The way it works is that after update returned a [model, fx] new view is created as root(view, model, address) then if driver is plugged it will update it's internal state with that VirtualRoot instance & schedule an animation frame & once animation frame is triggered VirtualRoot instances renderWith is invoked with actual virtual-dom implementation of that driver. So at the end of the day node, text and thunk just delegate to driver.node, driver.text and driver.thunk that does rendering. Only exception is if fragment of the virtual-tree is created outside of the view function (for example reusable static chunk is defined outside of view function) in such case LazyNode and friends are created, which are then .force()-ed as they get included inside actual virtual-dom tree.
signal.js
Provides simplistic implementation of FRP signals, if not familiar with FRP think of it as streams. Above mentioned start returns view, task and model signals. Drivers are plugged by subscribing to a signal. So in case of renderer, we subscribe to the view signal and every time we receive new value we schedule animation frame and update internal ref of what needs to be rendered. task signal similarly is a signal for requested effects (that init and update return). That also works with a driver, which performs those tasks and feeds results via actions into address.
task.js
Provides API very similar to Promise, primary difference is that it that it's not an eventual result of a computation but rather an eventual computation that driver can execute if so choses. Primary purpose of tasks is to do IO like XHR or even things like mutate DOM.
effects.js
This is kind of facility do to wiring / routing of tasks. When I mentioned that init / update return [model, fx] the fx part was instance of Effects. Effects just allow you to tag results of tasks they wrap, or batch several of them together. Or just imply no effects via Effects.none.
@tschneidereit Any feedback of possible bottlenecks would be great.
application.js
I would start reading from this file. The
start
function is what wires everything up.beginner
is just a functions that transform trivial app configuration the the one thatstart
expects.Configuration passed to
start
is expected to containflags
(which some data that is just forwarded to)init
function,update
andview
functions.init
supposed to construct initial state and request any kind of side effects (usually io task) to be performed to bootstrap. There for it returns[model, fx]
pair.model
then gets rendered viaview(model, address)
. Wheremodel
is whatever the immediate state of the app is andaddress
is a function that could be used to trigger more updates. Any user just passesaction
describing it to theaddress
. In turn receivedaction
triggersupdate(model, action)
that supposed to return[model, fx]
pair likeinit
did. View itself returns virtual-dom describing the application UI.One semi-confusing thing is that functions used to build virtual-dom tree (see
dom.js
) provide implementations ofnode
,text
andthunk
that either just delegate to same named functions on mysteriousdriver
or just return boxes that do that on.force()
. This is to facilitate swappable drivers for rendering. The way it works is that afterupdate
returned a[model, fx]
new view is created asroot(view, model, address)
then if driver is plugged it will update it's internal state with thatVirtualRoot
instance & schedule an animation frame & once animation frame is triggeredVirtualRoot
instancesrenderWith
is invoked with actual virtual-dom implementation of that driver. So at the end of the daynode
,text
andthunk
just delegate todriver.node
,driver.text
anddriver.thunk
that does rendering. Only exception is if fragment of the virtual-tree is created outside of theview
function (for example reusable static chunk is defined outside of view function) in such caseLazyNode
and friends are created, which are then.force()
-ed as they get included inside actual virtual-dom tree.signal.js
Provides simplistic implementation of FRP signals, if not familiar with FRP think of it as streams. Above mentioned
start
returnsview
,task
andmodel
signals. Drivers are plugged by subscribing to a signal. So in case of renderer, we subscribe to theview
signal and every time we receive new value we schedule animation frame and update internal ref of what needs to be rendered.task
signal similarly is a signal for requested effects (thatinit
andupdate
return). That also works with a driver, which performs those tasks and feeds results via actions intoaddress
.task.js
Provides API very similar to
Promise
, primary difference is that it that it's not an eventual result of a computation but rather an eventual computation that driver can execute if so choses. Primary purpose of tasks is to do IO like XHR or even things like mutate DOM.effects.js
This is kind of facility do to wiring / routing of tasks. When I mentioned that
init
/update
return[model, fx]
thefx
part was instance ofEffects
.Effects
just allow you to tag results of tasks they wrap, or batch several of them together. Or just imply no effects viaEffects.none
.