{-# LANGUAGE OverloadedStrings #-} {-| This module provides a few functions for conveniently serving Elm files through the Snap web framework. Any changes made to the served files will be reflected in the browser upon a refresh. The easiest way to get started is to use the default ElmOptions: > app = makeSnaplet ... $ do > opts <- defaultElmOptions > ... > addRoutes $ routes opts > ... Then, provide routes to the Elm runtime, and to any Elm files you wish to serve. > routes opts = > [ ("/elm", serveElm opts "static/elm/test.elm") > , ... > , serveElmRuntime opts > ] Additionally, you can customize the URI of the Elm runtime, the file path to the Elm runtime, or the paths to the directores that Elm will use to build and cache the compiled files. > app = makeSnaplet ... $ do > opts <- mkElmOptions > "route/to/use/for/runtime.js" > (Just "/my/own/local/file/for/the/actual/runtime.js") > (Just "/tmp/location/for/build/dir") > (Just "/tmp/location/for/cache/dir") > ... > addRoutes $ routes opts > ... |-} module Snap.Elm where ------------------------------------------------------------------------------ import Control.Monad import Control.Monad.IO.Class import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as C8 import Data.Maybe (fromMaybe) import Data.Monoid import qualified Data.Text as T import Snap.Core import Snap.Util.FileServe ------------------------------------------------------------------------------ import qualified Language.Elm as Elm import System.Exit import System.FilePath import System.Process ------------------------------------------------------------------------------ -- | A set of options to coordinate the serving of Elm files and runtime. data ElmOptions = ElmOptions { elmRuntimeURI :: ByteString , elmRuntimePath :: FilePath , elmBuildPath :: FilePath , elmCachePath :: FilePath } -- | The default set of options for serving Elm files. -- This will use "static/js/elm-runtime.js" as the URI -- for the Elm runtime, so you should use a custom route -- if the route conflicts with another, for some reason. defaultElmOptions :: MonadIO m => m ElmOptions defaultElmOptions = mkElmOptions "static/js/elm-runtime.js" Nothing Nothing Nothing -- | Construct a custom set of options. mkElmOptions :: MonadIO m => ByteString -- ^ Route at which to serve Elm runtime -> Maybe FilePath -- ^ 'FilePath' to custom Elm runtime -> Maybe FilePath -- ^ 'FilePath' to custom build directory -> Maybe FilePath -- ^ 'FilePath' to custom cache directory -> m ElmOptions mkElmOptions uri mr mb mc = do rt <- maybe (liftIO Elm.runtime) return mr let bp = fromMaybe "elm-build" mb let cp = fromMaybe "elm-cache" mc return $ ElmOptions uri rt bp cp -- | Serve an Elm file. The 'ElmOptions' argument can be -- constructed at the initialization of your app. serveElm :: MonadSnap m => ElmOptions -> FilePath -> m () serveElm opts fp = when (takeExtension fp == ".elm") $ do let args = [ "--make" , "--runtime=" ++ runtimeURI , "--build-dir=" ++ buildPath , "--cache-dir=" ++ cachePath , fp ] (ec,out,err) <- liftIO $ readProcessWithExitCode "elm" args "" case ec of ExitFailure _ -> writeText $ T.unlines [ "Failed to build Elm file (" <> T.pack fp <> "):" , T.pack out , T.pack err ] ExitSuccess -> serveFile (buildPath replaceExtension fp "html") where buildPath = elmBuildPath opts cachePath = elmCachePath opts runtimeURI = C8.unpack $ elmRuntimeURI opts -- | A route handler for the Elm runtime. If given the 'ElmOptions' used -- by 'serveElm', it will place the runtime at the route the Elm file -- will expect, as per the