y-taka-23 / miso-tutorial-app

An example GHCJS + Miso single page application. 🍜

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Example SPA in Miso

Docker Automated build Docker Build Status

An example GHCJS + Miso single page application.

screenshot

Running the Application

Docker

The application is packed as a Docker image, so you can launch it with a single command.

docker run -d -p 4000:4000 ytaka23/miso-tutorial-app:latest

Access http://localhost:4000.

Source

You can also build the application from source code. Note that, if it's your first experience of GHCJS, it takes a long time to set up the compiler.

git clone https://github.com/y-taka-23/miso-tutorial-app.git
cd miso-tutorial-app
./run.sh

The script prepares compilation environments according to each stack.yaml, builds both of the client/server side stuffs and starts the web server. Access http://localhost:4000.

Highlights

This project is ported from Elm implementation. Miso framework aims to provide "The Elm Architecture" for GHCJS, thus its architecture looks very close to the original Elm application. Nevertheless, the Miso implementation has several interesting differences.

Isomorphism

Isomorphism, also known as server side rendering (SSR,) is one of the most remarkable features of Miso framework. Implementing both of the client and server sides in the same language, that is Haskell, Miso can reuse most of the application and optimize the cost of renering.

As with The Elm Architecture, an isomorphic Miso application consists of the following parts:

  • Model stores states of the application.
  • Action triggers a state transition with a payload (Msg in Elm.)
  • Update makes a transit from the current state to the next, according to the incoming action.
  • Subscription catches mainly user's input.
  • Effect outputs anything to the outside world (Cmd in Elm.)
  • View converts the model to an HTML response.
  • Routing determines which page should be rendered.

In Miso isomorphism, the models, actions and views are "shared" between the client/server sides. When a browser accesses the web server for the first time, the server returns an HTML file built from the initial model and the view logic. Next, when the user changes the current URL (by a subscription) or accesses external APIs (by an effect,) JavaScript re-renders the page by the shared view and routing logic in the client side only.

Client Shared Server
Update Model (Servant Server)
Subscription Action
Effect View
Routing

Path-based Routing by Servant API

On the top of Haskell's powerful type system, Miso can denote its routing as types, in the similar way with Servant. The following defines three routes : /, /players and /players/:id as a single page application.

type Route =
         TopRoute
    :<|> ListRoute
    :<|> EditRoute

type TopRoute = View Action

type ListRoute = "players" :> View Action

type EditRoute = "players" :> Capture "ident" PlayerId :> View Action

Besides, for the server side, Miso converts and extends the client side routes to define the APIs. In the following snippet there are four APIs:

  • JsonApi returns JSON files for XHR by Miso effects; /api/players and /api/players/:id.
  • IsomorphicApireturns HTML files generated by SSR.
  • StaticApi returns static resources including JavaScript.
  • NotFoundApi returns the 404 error page.
type Api = JsonApi :<|> IsomorphicApi :<|> StaticApi :<|> NotFoundApi

type JsonApi =
         "api" :> "players" :> Get '[JSON] [Player]
    :<|> "api" :> "players" :> Capture "id" PlayerId
            :> ReqBody '[JSON] Player :> Put '[JSON] NoContent

type IsomorphicApi = ToServerRoutes Route HtmlPage Action

type StaticApi = "static" :> Raw

type NotFoundApi = Raw

Effects in IO Monads

In The Elm Architecture, the update function has a type:

update : Msg -> Model -> (Model, Cmd Msg)

On the other hand in Miso:

updateModel :: Action -> Model -> Effect Action Model

The Effect type is a sort of aliases, to handle IO actions asynchronously, implemented as:

data Effect action model = Effect model [Sub action]
type Sub action = Sink action -> IO ()
type Sink action = action -> IO ()

It means that Miso can do arbitrary IO actions as effects. Moreover it provides several constructors to simply denote update with/without effects:

(<#) :: model -> IO action -> Effect action model
noEff :: model -> Effect action model

Let's back to the example. Here fetchPlayers is just an IO action to fetch a player list via external connection. updateModel passes the result to SetPlayers, then again updateModel sets the player list without effects.

fetchPlayers :: IO (Either String [Player])
updatePlayer :: Either String Player -> Model -> Model

updateModel FetchPlayers m = m <# do
    SetPlayers <$> fetchPlayers
updateModel (SetPlayer eP) m = noEff $ updatePlayer eP m

For more detail, see the implementation.

References

About

An example GHCJS + Miso single page application. 🍜

License:BSD 3-Clause "New" or "Revised" License


Languages

Language:Haskell 95.2%Language:Shell 4.8%