{-# OPTIONS_HADDOCK show-extensions #-} {-| Module : Language.Elm.Build Description : Compile Elm code to JS within Haskell Copyright : (c) Joey Eremondi 2014 License : BSD3 Maintainer : joey@eremondi.com Stability : experimental This library provides both runtime and Template Haskell functions which let you take multi-module Elm Programs and compile them to JavaScript. The main goal of this is to allow Elm to be used as a frontend for Haskell servers or Web apps. The library is independent of any specific framework, so it should work with Yesod, Snap, Happstack, Scotty, etc. -} module Language.Elm.Build ( compileAll , standalone , addHeader ,deriveElmJS , elmQuasi , compileAndLinkAll ) where import qualified Data.Map as Map import Elm.Compiler import Elm.Compiler.Module import qualified Data.List as List import qualified Data.Set as Set import qualified Data.Map as Map import Control.Monad (filterM) import Data.Maybe (fromJust) import qualified Language.Haskell.TH as TH import Language.Haskell.TH.Quote import qualified Language.Elm.BuildUtil as Util import Language.Elm.CoreLibs import qualified Elm.Compiler.Module as Module import Data.List (intercalate) {-| Given a list of strings containing the source code for elm modules, return a dictionary mapping names to their compiled JavaScript source. (This allows you to staticaly serve commonly used modules, such as the runtime). The runtime is included in this dictionary, with the key \"Elm.Native.Runtime\". Gives a string error in the event of failure. -} compileAll :: [String] -> Either String (Map.Map Module.Name String) compileAll modules = do depPairs <- mapM Util.uniqueDeps modules let ourDeps = Map.fromList depPairs let moduleDict = Map.fromList $ zip (map fst depPairs) modules ourStdlib <- stdLibForSources ourDeps modules ourNatives <- nativesForSources ourDeps (sources, _ifaces) <- Util.compileAll ourDeps "" "" ourStdlib moduleDict let sourcesWithNatives = Map.insert (fst runtime) (snd runtime) (Map.union sources ourNatives) return sourcesWithNatives {-| Given a list of strings containing the source code for elm modules, compile the modules and bundle the result of compilation into a single, standalone, JavaScript source file, including the Elm-runtime and the Elm header. Gives a string error in the event of failure. -} compileAndLinkAll :: [String] -> Either String String compileAndLinkAll modules = standalone `fmap` (compileAll modules) {-| Bundle the result of compilation into a single, standalone, JavaScript source file, including the Elm-runtime and the Elm header -} standalone :: (Map.Map Module.Name String) -> String standalone result = let sortedNames = List.sort $ map fst $ Map.toList result sortedSources = map (\n -> result Map.! n) sortedNames in addHeader $ intercalate "\n" sortedSources {-| Add the JavaScript header which initializes the Elm object and allows sources to work together. -} addHeader :: String -> String addHeader = (header ++) -- | -- Derives the Template Haskell String literal correspondeing to the compiled and linked -- JavaScript for a given module -- For example: -- -- > {-#Language QuasiQuotes#-} -- > myElmJS :: String -- > myElmJS = [elmQuasi| -- > module Main where -- > x = 3 -- > |] -- -- -- This function is useful for small code snippets, but when dealing with -- multi-module Elm projects, deriveElmJS should be used. -- Note that the elm code is compiled to JS at when your Haskell code is compiled. elmQuasi :: QuasiQuoter elmQuasi = QuasiQuoter { quoteExp = \s -> deriveElmJS [s], quotePat = \_s -> error "Can't use Elm quasiQuoter in pattern position", quoteType = \_s -> error "Can't use Elm quasiQuoter in type position", quoteDec = \_s -> error "Can't use Elm quasiQuoter in dec position" } -- | -- Derives the Template Haskell String literal correspondeing to the compiled and linked -- JavaScript from compiling the given modules. -- For example: -- -- > {-# LANGUAGE TemplateHaskell #-} -- > myElmJS :: String -- > myElmJS = $(deriveElmJS [myElmMain, someElmHelperLib]) -- -- The main use of this is to ensure that your Elm code is compiled into JavaScript -- at the same time your Haskell code is compiled. deriveElmJS :: [String] -> TH.ExpQ deriveElmJS modules = case (compileAndLinkAll modules) of Right s -> TH.litE $ TH.stringL s Left err -> error $ "Error compiling Elm code:\n" ++ err