Commit 941fcb4d authored by julien dehos's avatar julien dehos

clean repo + readme

parent 0365eb86
# miso-xhr
![](miso-xhr.png)
## description
- isomorphic web app in Haskell, using Miso and Servant
- fetch data using XHR
- type-safe routes & links (client + server)
## usage
Install Nix, then run `make`.
name: miso-xhr
version: 0.1
build-type: Simple
cabal-version: >=1.10
license: PublicDomain
executable server
if impl(ghcjs)
buildable: False
ghc-options: -O2 -threaded -Wall
main-is: server.hs
hs-source-dirs: src
other-modules: Common
default-language: Haskell2010
build-depends: aeson,
base,
binary,
lucid,
miso,
network-uri,
servant,
servant-server,
wai,
wai-extra,
warp
executable client
if !impl(ghcjs)
buildable: False
ghcjs-options: -dedupe -O2 -Wall
main-is: client.hs
hs-source-dirs: src
other-modules: Common
default-language: Haskell2010
build-depends: aeson,
base,
ghcjs-base,
miso,
network-uri,
servant
let
bootstrap = import <nixpkgs> {};
nixpkgs-src = bootstrap.fetchFromGitHub {
owner = "NixOS";
repo = "nixpkgs";
rev = "3fd87ad0073fd1ef71a8fcd1a1d1a89392c33d0a";
sha256 = "0n4ffwwfdybphx1iyqz1p7npk8w4n78f8jr5nq8ldnx2amrkfwhl";
};
servant-src = bootstrap.fetchFromGitHub {
owner = "haskell-servant";
repo = "servant";
rev = "v0.15";
sha256 = "0n9xn2f61mprnvn9838zbl4dv2ynnl0kxxrcpf5c0igdrks8pqws";
};
miso-src = bootstrap.fetchFromGitHub {
owner = "haskell-miso";
repo = "miso";
rev = "0.21.2.0";
sha256 = "07k1rlvl9g027fp2khl9kiwla4rcn9sv8v2dzm0rzf149aal93vn";
};
config = {
packageOverrides = pkgs: rec {
haskell = pkgs.haskell // {
packages = pkgs.haskell.packages // {
ghc = pkgs.haskell.packages.ghc844.override {
overrides = self: super: with pkgs.haskell.lib; {
miso = self.callCabal2nix "miso" miso-src {};
};
};
} // {
# Many packages don't build on ghcjs because of a dependency on doctest
# (which doesn't build), or because of a runtime error during the test run.
# See: https://github.com/ghcjs/ghcjs/issues/711
ghcjs = pkgs.haskell.packages.ghcjs84.override {
overrides = self: super: with pkgs.haskell.lib; {
tasty-quickcheck = dontCheck super.tasty-quickcheck;
http-types = dontCheck super.http-types;
comonad = dontCheck super.comonad;
semigroupoids = dontCheck super.semigroupoids;
lens = dontCheck super.lens;
miso = self.callCabal2nix "miso" miso-src {};
servant = dontCheck (doJailbreak (self.callCabal2nix "servant" (servant-src + "/servant") {}));
};
};
};
};
};
};
in
import nixpkgs-src { inherit config; }
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
module Common where
import Data.Aeson (FromJSON, ToJSON)
import GHC.Generics (Generic)
import Miso
import qualified Miso.String as MS
import Servant.API
data Hero = Hero
{ heroName :: MS.MisoString
, heroImage :: MS.MisoString
} deriving (Eq, Generic)
instance FromJSON Hero
instance ToJSON Hero
newtype Model = Model
{ heroes_ :: [Hero]
} deriving (Eq)
initialModel :: Model
initialModel = Model []
data Action
= NoOp
| SetHeroes [Hero]
| FetchHeroes
deriving (Eq)
type HeroesApi = "heroes" :> Get '[JSON] [Hero]
type ClientRoutes = HomeRoute
type HomeRoute = View Action
homeRoute :: Model -> View Action
homeRoute m = div_
[]
[ h1_ [] [ text "miso-xhr" ]
, ul_ [] (map fmtHero $ heroes_ m)
, p_ [] [ a_ [href_ "heroes" ] [ text "JSON data" ] ]
]
where fmtHero h = li_ []
[ text $ heroName h
, br_ []
, img_ [ src_ $ MS.concat ["/static/", heroImage h] ]
]
{-# LANGUAGE OverloadedStrings #-}
import Common
import Data.Aeson (decodeStrict)
import Data.Maybe (fromJust, fromMaybe)
import JavaScript.Web.XMLHttpRequest
import Miso
main :: IO ()
main = miso $ const App
{ initialAction = FetchHeroes
, model = initialModel
, update = updateModel
, view = homeRoute
, events = defaultEvents
, subs = []
, mountPoint = Nothing
}
updateModel :: Action -> Model -> Effect Action Model
updateModel NoOp m = noEff m
updateModel (SetHeroes heroes) m = noEff m { heroes_ = heroes }
updateModel FetchHeroes m = m <# do SetHeroes <$> xhrHeroes
xhrHeroes :: IO [Hero]
xhrHeroes =
fromMaybe [] . decodeStrict . fromJust . contents <$> xhrByteString req
where req = Request GET "heroes" Nothing [] False NoData
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
import Common
import Data.Proxy (Proxy(..))
import qualified Lucid as L
import Miso
import Network.Wai.Handler.Warp (run)
import Network.Wai.Middleware.RequestLogger (logStdout)
import Servant
main :: IO ()
main = run 3000 $ logStdout $ serve (Proxy @ServerApi) server
type ServerApi
= "static" :> Raw
:<|> HeroesApi
:<|> ToServerRoutes ClientRoutes HtmlPage Action
server :: Server ServerApi
server
= serveDirectoryFileServer "static"
:<|> pure heroes
:<|> (pure $ HtmlPage $ homeRoute initialModel)
heroes :: [Hero]
heroes =
[ Hero "Scooby Doo" "scoobydoo.png"
, Hero "Sponge Bob" "spongebob.png"
]
newtype HtmlPage a = HtmlPage a
deriving (Show, Eq)
instance L.ToHtml a => L.ToHtml (HtmlPage a) where
toHtmlRaw = L.toHtml
toHtml (HtmlPage x) = L.doctypehtml_ $ do
L.head_ $ do
L.meta_ [L.charset_ "utf-8"]
L.with
(L.script_ mempty)
[L.src_ "static/all.js", L.async_ mempty, L.defer_ mempty]
L.body_ (L.toHtml x)
all:
nix-shell -A client --run "cabal --builddir=dist-client --config-file=client.config build"
mkdir -p static
ln -sf ../dist-client/build/client/client.jsexe/all.js static/
nix-shell -A server --run "cabal --builddir=dist-server --config-file=server.config run server"
# build client, in a "nix-shell -A client"
client:
cabal --builddir=dist-client --config-file=client.config build
mkdir -p static
ln -sf ../dist-client/build/client/client.jsexe/all.js static/
# run server, in a "nix-shell -A server"
server:
cabal --builddir=dist-server --config-file=server.config run server
clean:
rm -rf dist-* static/all.js
let
pkgs = import ./nixpkgs.nix ;
server = pkgs.haskell.packages.ghc.callCabal2nix "miso-xhr" ./. {};
client = pkgs.haskell.packages.ghcjs.callCabal2nix "miso-xhr" ./. {};
in
{
server = server.env;
client = client.env;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment