miso
A tasty Haskell front-end framework
Miso is a small "isomorphic" Haskell front-end framework featuring a virtual-dom, diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe servant-style routing and an extensible Subscription-based subsystem. Inspired by Elm, Redux and Bobril. Miso is pure by default, but side effects (like XHR
) can be introduced into the system via the Effect
data type. Miso makes heavy use of the GHCJS FFI and therefore has minimal dependencies.
Table of Contents
Quick start
To get started quickly building applications, we recommend using the stack
or nix
package managers. Obtaining GHCJS
is required as a prerequisite. stack
and nix
make this process easy, if you're using cabal
we assume you have obtained GHCJS
by other means.
All source code depicted below for the quick start app is available here.
Stack
In the miso
repository there is a folder named stack
with "known to work" configurations for GHCJS
. One stack file exists for both the 7.10.3
and 8.0.1
versions of GHCJS
. In general, we recommend developing with the 7.10.3
version since it currently supports GHCJSi
(a REPL that connects to the browser by way of a nodejs
web server using socket.io
) and building with the 8.0.1
version (if possible). For more information on using stack
with GHCJS
, please consult the GHCJS section of the stack
docs.
To begin, create the following directory layout
➜ mkdir app && touch app/{Main.hs,app.cabal,stack.yaml} && tree app
app
|-- Main.hs
|-- app.cabal
`-- stack.yaml
Add a stack.yaml
file that uses a recent version of miso
.
➜ cat app/stack.yaml
resolver: lts-6.20
compiler: ghcjs-0.2.0.9006020_ghc-7.10.3
compiler-check: match-exact
packages:
- '.'
extra-deps:
- miso-0.7.2.0
setup-info:
ghcjs:
source:
ghcjs-0.2.0.9006020_ghc-7.10.3:
url: http://ghcjs.tolysz.org/lts-6.20-9006020.tar.gz
sha1: a6cea90cd8121eee3afb201183c6e9bd6bacd94a
Add a cabal
file
➜ cat app/*.cabal
name: app
version: 0.1.0.0
synopsis: First miso app
category: Web
build-type: Simple
cabal-version: >=1.10
executable app
main-is: Main.hs
build-depends: base, miso
default-language: Haskell2010
Add the source from Sample Application to app/Main.hs
Run stack setup
. This might take a long time, since it will have to build GHCJS
.
stack setup
Run stack build
to get the static assets
stack build
See the result
open $(stack path --local-install-root)/bin/app.jsexe/index.html
Using GHCJSi
stack ghci
If that warns with socket.io not found, browser session not available
, you'll need to install socket.io
npm install socket.io
and update your NODE_PATH
export NODE_PATH=$(pwd)/node_modules
Now you should be connected, and the app viewable in GHCJSi
(open http://localhost:6400).
➜ stack ghci
app-0.1.0.0: initial-build-steps (exe)
Configuring GHCi with the following packages: app
GHCJSi, version 0.2.0.9006020-7.10.3: http://www.github.com/ghcjs/ghcjs/ :? for help
[1 of 1] Compiling Main ( /Users/david/Desktop/miso/sample-app/Main.hs, interpreted )
socket.io found, browser session available at http://localhost:6400
Ok, modules loaded: Main.
*Main> main
browser connected, code runs in browser from now on
Nix
Nix
is a more powerful option for building web applications with miso
since it encompasses development workflow, configuration management, and deployment. The source code for haskell-miso.org
is an example of this.
If unfamiliar with nix
, we recommend @Gabriel439's "Nix and Haskell in production" guide.
To get started, we will use the cabal2nix
tool to convert our Cabal
file into a nix
derivation (named app.nix
). We'll then write a file named default.nix
, which is used for building our project (via nix-build
) and development (via nix-shell
).
To begin, make the following directory layout:
➜ mkdir app && touch app/{Main.hs,app.cabal,default.nix,app.nix} && tree app
app
|-- Main.hs
|-- app.cabal
|-- default.nix
`-- app.nix
Add a cabal
file
➜ cat app/*.cabal
name: app
version: 0.1.0.0
synopsis: First miso app
category: Web
build-type: Simple
cabal-version: >=1.10
executable app
main-is: Main.hs
build-depends: base, miso
default-language: Haskell2010
Use cabal2nix
to generate a file named app.nix
that looks like below.
➜ cabal2nix . --compiler ghcjs > app.nix
➜ cat app.nix
{ mkDerivation, base, miso, stdenv }:
mkDerivation {
pname = "app";
version = "0.1.0.0";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [ base miso ];
description = "First miso app";
license = stdenv.lib.licenses.unfree;
}
Write a default.nix
(which calls app.nix
), this fetches a recent version of miso
.
{ pkgs ? import <nixpkgs> {} }:
let
result = import (pkgs.fetchFromGitHub {
owner = "dmjio";
repo = "miso";
sha256 = "13ckz11gbfs047hl3phj7h6fm59wsg9zw2fiqjaqkxmxv17zj5yj";
rev = "0834d5c0b309de24d836cbdcc25fd257de10be17";
}) {};
in pkgs.haskell.packages.ghcjs.callPackage ./app.nix {
miso = result.miso-ghcjs;
}
Build the project
nix-build
Open the result
open ./result/bin/app.jsexe/index.html
For development with nix
, it's important to have cabal
present for building. This command will make it available in your PATH
.
nix-env -iA cabal-install -f '<nixpkgs>'
To be put into a shell w/ GHCJS
and all the dependencies for this project present, use nix-shell
.
nix-shell -A env
To open GHCJSi
(NODE_PATH
should already be set properly)
$ cabal configure --ghcjs
$ cabal repl
Package has never been configured. Configuring with default flags. If this
fails, please run configure manually.
Resolving dependencies...
Configuring app-0.1.0.0...
Preprocessing executable 'app' for app-0.1.0.0...
GHCJSi, version 0.2.0-7.10.3: http://www.github.com/ghcjs/ghcjs/ :? for help
[1 of 1] Compiling Main ( Main.hs, interpreted )
Ok, modules loaded: Main.
*Main>
browser connected, code runs in browser from now on
Cabal
The latest stable version of miso
will be available on Hackage.
To build with cabal, we assume ghcjs
is in your PATH
and ghcjs-base
is present in your ghcjs-pkg
list.
cabal sandbox init
cabal install --ghcjs
cabal build
open dist/build/app/app.jsexe/index.html
GHCJSi Caveats
If you run main
in GHCJSi
, interrupt it and then run it again, you
will end up with two copies of your app displayed above each other. As
a workaround, you can use clearBody >> main
which will completely
clear the document body before rendering your application.
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. For more info on how to use stack
with a client
/server
setup, see this link. For more information on how to use nix
with a client
/server
setup, see the nix scripts for [https://haskell-miso.org](https://haskell-miso.org).
Examples
TodoMVC
Flatris
2048
Snake
Mario
Websocket
SSE
XHR
Router
SVG
Canvas 2D
ThreeJS
Simple
File Reader
Haddocks
GHCJS
GHC
Sample application
-- | Haskell language pragma
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Haskell module declaration
module Main where
-- | Miso framework import
import Miso
import Miso.String
-- | Type synonym for an application model
type Model = Int
-- | Sum type for application events
data Action
= AddOne
| SubtractOne
| NoOp
| SayHelloWorld
deriving (Show, Eq)
-- | Entry point for a miso application
main :: IO ()
main = startApp App {..}
where
initialAction = SayHelloWorld -- initial action to be executed on application load
model = 0 -- initial model
update = updateModel -- update function
view = viewModel -- view function
events = defaultEvents -- default delegated events
subs = [] -- empty subscription list
-- | Updates model, optionally introduces side effects
updateModel :: Action -> Model -> Effect Action Model
updateModel AddOne m = noEff (m + 1)
updateModel SubtractOne m = noEff (m - 1)
updateModel NoOp m = noEff m
updateModel SayHelloWorld m = m <# do
putStrLn "Hello World" >> pure NoOp
-- | Constructs a virtual DOM from a model
viewModel :: Model -> View Action
viewModel x = div_ [] [
button_ [ onClick AddOne ] [ text "+" ]
, text (ms (show x))
, button_ [ onClick SubtractOne ] [ text "-" ]
]
Building examples
The easiest way to build the examples is with the nix
package manager
git clone https://github.com/dmjio/miso && cd miso && nix-build
This will build all examples and documentation into a folder named result
➜ miso git:(master) ✗ tree result -d
result
|-- doc
| |-- x86_64-osx-ghc-8.0.2
| | `-- miso-0.2.0.0
| | `-- html
| | `-- src
| `-- x86_64-osx-ghcjs-0.2.0-ghc7_10_3
| `-- miso-0.2.0.0
| `-- html
| `-- src
|-- examples
| |-- mario.jsexe
| | `-- imgs
| | |-- jump
| | |-- stand
| | `-- walk
| |-- router.jsexe
| |-- simple.jsexe
| |-- tests.jsexe
| |-- todo-mvc.jsexe
| `-- websocket.jsexe
To see examples, we recommend hosting them with a webserver
cd result/examples/todo-mvc.jsexe && python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
Maintainers
@dmjio
Contributing
Feel free to dive in! Open an issue or submit PRs.
See CONTRIBUTING for more info.
License
BSD3 © David Johnson