Backends

Render backends are pluggable in Shpadoinkle. This project currently comes with three backends:

ParDiff

Gitlab

Haddock

Snabbdom

Gitlab

Haddock

Static

Gitlab

Haddock

You do not need to change your application to use any of these. Our view code will work without alteration with any backend. The application should still build and run with both GHC and GHCjs.

Using a Backend

Take a quick look at the shpadoinkle primitive:

shpadoinkle :: Backend b m a => Territory t => Eq a
  => (m ~> JSM) -> (t a -> b m ~> m) -> a -> t a -> (a -> Html (b m) a) -> b m RawNode -> JSM ()

The second argument (t a → b m ~> m) is how you choose your backend. For example:

import Shpadoinkle.Backend.ParDiff
shpadoinkle id runParDiff 0 territory view getBody

This will use the ParDiff Virtual DOM backend written in Haskell by simply changing it to:

import Shpadoinkle.Backend.Snabbdom
shpadoinkle id runSnabbdom 0 territory view getBody

Now the application will render with the Snabbdom Virtual DOM backend written in JavaScript instead.

The Static backend is a bit different as it does not work with the shpadoinkle primitive. Instead it exposes a single function:

renderStatic :: Html m a -> Text

Writing a Backend

Adding your own backend is a matter of writing an instance of the Backend type class located in core.

class Backend b m a | b m -> a where
  type VNode b m
  interpret :: (m ~> JSM) -> Html (b m) a -> b m (VNode b m)
  patch     :: RawNode -> Maybe (VNode b m) -> VNode b m -> b m (VNode b m)
  setup     :: JSM () -> JSM ()

This interface lets you plug into various rendering systems. So long as you can provide implementations of these three functions, you can use shpadoinkle to get an application out of Html.

This packages does not come with a backend implementation, and an implementation is required to run the shpadoinkle function.

Monad Transformer

b is expected to be a Monad Transformer, though this is not required; in practice, (b m) must have an instance of MonadJSM.

VNode

This type family points maps to the underlying representation native to the backend:

type VNode b m

In the case of binding to a JavaScript library, this would most likely be a newtype of JSVal. When binding to a typed implementation, this should just be set to the library type.

Interpret

This function describes how to marshal between Html and the native representation (i.e. VNode):

interpret
  :: (m ~> JSM) (1)
  -> Html (b m) a (2)
  -> b m (VNode b m) (3)

The interpret function can be Monadic, as it is likely going to require IO to obtain the native representation.

1 Interpret is provided with a mechanism for getting from the end user provided Monad to JSM directly.
2 The Html Shpadoinkle view that needs to be marshalled to the native representation for this backend.
3 A Monadic action that generates VNode.

Patch

This function describes how updates are handled:

patch
  :: RawNode (1)
  -> Maybe (VNode b m) (2)
  -> VNode b m (3)
  -> b m (VNode b m) (4)

The interpret function can be Monadic, as it is likely going to require IO to apply the new VNode to the view.

1 This is the parent DOM Node that contains the application. RawNode is a newtype of JSVal.
2 The previously rendered VNode. On the first rendering of the application, this will be Nothing.
3 The VNode the user would like to render.
4 A Monadic action that actually renders in the browser and returns a new VNode. The returned (v :: VNode) will be (Just v) for 2 in the next render.

Setup

This is an optional IO action to perform any initial setup steps a given backend might require:

setup
  :: JSM () (1)
  -> JSM ()
1 This is a callback you are responsible for executing after the setup process is complete. The callback is the entire application. If you do not evaluate the JSM (), then nothing will happen.

In the case of JavaScript-based backends, it will likely include steps like adding the library to the <head> of the page, or instantiating a JavaScript class.