Skip to content

Commit

Permalink
Add Internals.md documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
dmjio committed Feb 8, 2025
1 parent e411f3e commit db239b9
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- [Begin](#begin)
- [Nix](#nix)
- [Architecture](#architecture)
- [Under the hood](#under-the-hood)
- [Examples](#examples)
- [TodoMVC](#todomvc)
- [Flatris](#flatris)
Expand Down Expand Up @@ -178,6 +179,10 @@ ag -l | entr sh -c 'cabal build'
### Architecture
For constructing client and server applications, we recommend using one `cabal` file with two executable sections, where the `buildable` attribute set is contingent on the compiler. An example of this layout is [here](https://github.com/dmjio/miso/blob/master/examples/haskell-miso.org/haskell-miso.cabal#L16-L60). For more info on how to use `stack` with a `client`/`server` setup, see this [link](https://docs.haskellstack.org/en/stable/ghcjs/#project-with-both-client-and-server). For more information on how to use `nix` with a `client`/`server` setup, see the [nix scripts](https://github.com/dmjio/miso/blob/master/examples/haskell-miso.org/default.nix) for [https://haskell-miso.org](https://haskell-miso.org).

## Under the hood

For details of the internals and general overview of how `miso` works, see the [Internals](docs/Internals.md).

## Examples

### TodoMVC
Expand Down
46 changes: 46 additions & 0 deletions docs/Internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Internals
===========================

## Overview

Miso’s external API has three main parts. The `model` (parameterized by `action`), the `view` function and `update` function.

## Concurrency

Under the hood miso’s concurrency model centers around an atomically updated `IORef (Seq action)` (known as queue). The queue is used to capture events and update the user-defined `model` [^1]. Each captured browser event causes a green thread [^2] to be forked, and an `action` appended to the queue.

## Event Loop

`miso` operates in an event [loop](https://github.com/dmjio/miso/blob/master/src/Miso.hs#L124) that blocks on [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame). The `model` lives on the stack of the event loop function. Inside this loop we drain the queue of `action`s, and fold them against the `model` (known as “batching”). We then diff the new `model` against the old `model`. Finally, if the `model` has been updated (is dirty), `view` is invoked on the updated `model` to get a `VTree` [^3]. This `VTree` gets converted to a JS tree and is diffed + patched against the existing tree (that lives on the JS heap) and the DOM gets updated. The loop recurses on the new `model`.

## Diffing and patching (simultaneously)

During [diffing](https://github.com/dmjio/miso/blob/master/jsbits/diff.js#L3) we mutually recurse over the parent and child nodes of both the old and new virtual DOM structures. During this process we diff the old tree against the new tree (while simultaneously updating the DOM to reflect the new structure) [^4]. We apply various new optimizations not seen in react on the child lists (see [syncChildren](https://github.com/dmjio/miso/blob/master/jsbits/diff.js#L187)). During diffing we also invoke node creation / destruction life cycle hooks.

## Events

While the event loop is executing, browser events are raised asynchronously and delegated into Haskell callbacks that live on the virtual DOM in the JS heap [^6]. The event body is parsed into a Haskell structure via JSON (FromJSON), the update function is invoked to produce an Action that gets written to the queue atomically. Event delegation and DOM diffing occur simultaneously.

## Misc. concurrency

`subs` and `sink` are ways to write into the actions queue externally (useful for integration with third party components). There are some predefined `subs` in `Miso.Subscription` for conveniently working with the History, Websocket, Keyboard and Mouse APIs.

## Pre-rendering

Pre-rendering (using the `miso` function) on application startup will traverse the DOM and copy pointers into miso’s virtual DOM structure (this process is known as hydration). This is necessary for events to work, since event delegation works by DOM pointer traversal on the virtual DOM to find the correct node to dispatch the event [^6].

## View

The `view` function is how templating works in miso. The View is a Rose tree of instructions that gets intelligently drawn on the screen in response to browser events that affect the `model`.

[^1]: `Seq` is used to aid event ordering and avoid excessive redraws.

[^2]: Green threads are very cheap in Haskell. The GHCJS and GHC RTS (w/ WASM backend) should have equivalent operating semantics for threads.

[^3]: Since events can be no-ops we want to avoid generating a tree if the model hasn’t changed. Miso uses both `Eq` and `StablePtr` equality to determine if a draw is necessary. The `StablePtr` equality is an optimization that avoids expensive calls to `(==)` on large models.

[^4]: This diff + patch approach is responsible for a lot of performance gains. We don't generate a list of patches and apply them in a separate phase like some other frameworks.

[^5]: `VTree` is the Haskell AST version of a JS virtual DOM. The `view` function constructs terms in this AST, it is then lowered into a `JSVal`. The `JSVal` is a virtual DOM tree structure that lives in the JS heap that is used for diffing. Once lowered, we diff against the existing virtual DOM that already lives in the JS heap.

[^6]: The event handlers that live on the JS virtual DOM are how miso calls back into the Haskell heap from the JS heap to write to the `Action` queue.

0 comments on commit db239b9

Please sign in to comment.