module Settings.StaticFiles.Webpack ( mkWebpackEntrypoints ) where import ClassyPrelude import Language.Haskell.TH import Language.Haskell.TH.Syntax hiding (Lift(..)) import qualified Language.Haskell.TH.Syntax as TH (Lift(..)) import qualified Data.Yaml as Yaml import qualified Data.Map as Map import Yesod.Core (Route, MonadLogger, MonadWidget, HandlerSite, logDebugS, logErrorS) import Yesod.EmbeddedStatic (EmbeddedStatic) import Yesod.EmbeddedStatic.Types import Network.Mime (MimeType) import Control.Lens.Indexed (iforM) import Control.Monad.Writer.Class (MonadWriter(..)) import Control.Monad.Trans.Writer.Lazy (execWriterT) import Control.Monad.Catch (MonadThrow(..)) import Utils (nubOn) import System.FilePath (makeRelative) import Text.Shakespeare.Text (st) mkWebpackEntrypoints :: FilePath -- ^ Path to YAML-manifest -> [FilePath -> Generator] -> FilePath -- ^ Path to static dir -> DecsQ mkWebpackEntrypoints manifest mkGen stDir = do addDependentFile manifest entrypoints <- decodeManifest manifest staticEntries <- concat <$> mapM ($ stDir) mkGen fmap (concat . Map.elems) . iforM entrypoints $ \entrypoint files -> do entries <- execWriterT . forM_ files $ \file -> do let fileEntries = filter (\entry -> makeRelative stDir (ebLocation entry) == file) staticEntries forM_ fileEntries $ \entry -> case ebHaskellName entry of Nothing -> lift . reportWarning $ concat [ "Entry “" , ebLocation entry , "” of file “" , file , "” of webpack entrypoint “" , entrypoint , "” has no haskellName" ] Just n -> tell $ pure (n, ebMimeType entry) let entryName = mkName $ "webpackEntrypoint_" <> entrypoint widgetName = mkName $ "webpackLinks_" <> entrypoint staticR <- newName "staticR" sequence [ sigD entryName [t|[(Route EmbeddedStatic, MimeType)]|] , funD entryName [ clause [] (normalB . listE . map (\(n, mime) -> tupE [varE n, TH.lift mime]) $ nubOn fst entries) [] ] , sigD widgetName [t|forall m. (MonadLogger m, MonadWidget m) => (Route EmbeddedStatic -> Route (HandlerSite m)) -> m ()|] , funD widgetName [ clause [varP staticR] (normalB [e| do $logDebugS "siteLayout" $ tshow $(varE entryName) forM_ $(varE entryName) $ \(sRoute, mime) -> let ctEq = (==) `on` simpleContentType in if | mime `ctEq` "text/css" -> addStylesheet $ $(varE staticR) sRoute | mime `ctEq` "application/javascript" -> addScript $ $(varE staticR) sRoute | otherwise -> $logErrorS "siteLayout" [st|Unknown mime type in webpack bundle: #{tshow mime}|] |]) [] ] ] where decodeManifest :: FilePath -> Q (Map String [FilePath]) decodeManifest manifest' = do res <- liftIO $ Yaml.decodeFileWithWarnings manifest' case res of Left exc -> throwM exc Right (ws, res') -> res' <$ mapM_ (\w -> reportWarning $ "Warning while parsing webpack manifest: " <> show w) ws