yesod-static-streamly 0.1.4.5 → 0.1.5.1
raw patch · 4 files changed
+192/−6 lines, 4 filesdep +data-defaultPVP ok
version bump matches the API change (PVP)
Dependencies added: data-default
API changes (from Hackage documentation)
+ Yesod.Static.Streamly: combineScriptsStreamly' :: Bool -> CombineSettingsStreamly -> Name -> [Route Static] -> Int -> Q Exp
+ Yesod.Static.Streamly: combineStylesheetsStreamly' :: Bool -> CombineSettingsStreamly -> Name -> [Route Static] -> Int -> Q Exp
+ Yesod.Static.Streamly.Internal: CSS :: CombineTypeStreamly
+ Yesod.Static.Streamly.Internal: CombineSettingsStreamly :: FilePath -> ([FilePath] -> ByteString -> IO ByteString) -> ([FilePath] -> ByteString -> IO ByteString) -> (Text -> IO Text) -> (Text -> IO Text) -> FilePath -> CombineSettingsStreamly
+ Yesod.Static.Streamly.Internal: JS :: CombineTypeStreamly
+ Yesod.Static.Streamly.Internal: [csCombinedFolder] :: CombineSettingsStreamly -> FilePath
+ Yesod.Static.Streamly.Internal: [csCssPostProcess] :: CombineSettingsStreamly -> [FilePath] -> ByteString -> IO ByteString
+ Yesod.Static.Streamly.Internal: [csCssPreProcess] :: CombineSettingsStreamly -> Text -> IO Text
+ Yesod.Static.Streamly.Internal: [csJsPostProcess] :: CombineSettingsStreamly -> [FilePath] -> ByteString -> IO ByteString
+ Yesod.Static.Streamly.Internal: [csJsPreProcess] :: CombineSettingsStreamly -> Text -> IO Text
+ Yesod.Static.Streamly.Internal: [csStaticDir] :: CombineSettingsStreamly -> FilePath
+ Yesod.Static.Streamly.Internal: base64md5Streamly :: ByteString -> String
+ Yesod.Static.Streamly.Internal: combineStaticsStreamly' :: CombineTypeStreamly -> CombineSettingsStreamly -> [Route Static] -> Int -> Q Exp
+ Yesod.Static.Streamly.Internal: data CombineSettingsStreamly
+ Yesod.Static.Streamly.Internal: data CombineTypeStreamly
+ Yesod.Static.Streamly.Internal: instance Data.Default.Class.Default Yesod.Static.Streamly.Internal.CombineSettingsStreamly
+ Yesod.Static.Streamly.Internal: liftRoutesStreamly :: [Route Static] -> Q Exp
Files
- CHANGELOG.md +5/−0
- src/Yesod/Static/Streamly.hs +30/−0
- src/Yesod/Static/Streamly/Internal.hs +155/−5
- yesod-static-streamly.cabal +2/−1
CHANGELOG.md view
@@ -71,3 +71,8 @@ ## 0.1.4.5 -- 2023-07-14 * Fixing documentation for Yesod.Static.Streamly.++## 0.1.5.1 -- 2023-07-17++* Added combineStylesheetsStreamly' and combineScriptsStreamly' replacement functions in Yesod.Static.Streamly.+* Added CombineTypeStreamly(..), CombineSettingsStreamly(..), liftRoutesStreamly, combineStaticsStreamly' and base64md5Streamly in Yesod.Static.Streamly.Internal.
src/Yesod/Static/Streamly.hs view
@@ -21,6 +21,9 @@ module Yesod.Static.Streamly ( -- * Yesod.Static Replacement functions - Smart constructor staticStreamly, staticDevelStreamly,+ -- * Yesod.Static Replacement functions - Combining CSS/JS+ combineStylesheetsStreamly',+ combineScriptsStreamly', -- * Yesod.Static Replacement functions - Template Haskell helpers staticFilesStreamly, staticFilesListStreamly,@@ -60,6 +63,33 @@ hashLookup <- cachedETagLookupDevelStreamly dir size return $ Static $ webAppSettingsWithLookup dir hashLookup++-- | A more performant replacement of+-- [combineStylesheets'](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html#v:combineStylesheets-39-)+-- found in [Yesod.Static](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html).+combineStylesheetsStreamly' :: Bool -- ^ development? if so, perform no combining+ -> CombineSettingsStreamly+ -> Name -- ^ Static route constructor name, e.g. \'StaticR+ -> [Route Static] -- ^ files to combine+ -> Int -- ^ buffer size (0.25 - 0.50 x your L2 cache seems to be best.)+ -> Q Exp+combineStylesheetsStreamly' development cs con routes size+ | development = [| mapM_ (addStylesheet . $(return $ ConE con)) $(liftRoutesStreamly routes) |]+ | otherwise = [| addStylesheet $ $(return $ ConE con) $(combineStaticsStreamly' CSS cs routes size) |]++-- | A more performant replacement of+-- [combineScripts'](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html#v:combineScripts-39-)+-- found in [Yesod.Static](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html).+combineScriptsStreamly' :: Bool -- ^ development? if so, perform no combining+ -> CombineSettingsStreamly+ -> Name -- ^ Static route constructor name, e.g. \'StaticR+ -> [Route Static] -- ^ files to combine+ -> Int -- ^ buffer size (0.25 - 0.50 x your L2 cache seems to be best.)+ -> Q Exp+combineScriptsStreamly' development cs con routes size+ | development = [| mapM_ (addScript . $(return $ ConE con)) $(liftRoutesStreamly routes) |]+ | otherwise = [| addScript $ $(return $ ConE con) $(combineStaticsStreamly' JS cs routes size) |]+ -- | A more performant replacement of -- [staticFiles](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html#v:staticFiles)
src/Yesod/Static/Streamly/Internal.hs view
@@ -47,7 +47,12 @@ notHiddenStreamly, getFileListPiecesStreamly, pathFromRawPiecesStreamly,+ CombineTypeStreamly(..),+ CombineSettingsStreamly(..),+ liftRoutesStreamly,+ combineStaticsStreamly', base64md5FileStreamly,+ base64md5Streamly, base64Streamly, hashFileStreamly, sinkHashStreamly@@ -58,21 +63,27 @@ import "cryptonite" Crypto.Hash.IO (HashAlgorithm) import qualified Data.ByteArray as ByteArray import Data.ByteString as B (ByteString)-import Data.ByteString.Lazy as L (ByteString)+import Data.ByteString.Lazy as L (ByteString,concat,writeFile) import qualified Data.ByteString.Base64 import qualified Data.ByteString.Char8 as S8 import Data.Char (isLower,isDigit)+import Data.Default import Data.IORef (readIORef,newIORef,writeIORef) import Data.List (foldl',intercalate,sort) import qualified Data.Map as M-import Data.Text (pack)+--import Data.Text (pack)+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TLE import Language.Haskell.TH import Language.Haskell.TH.Syntax as TH import qualified Streamly.Data.Stream as S import Streamly.External.ByteString.Lazy as StreamlyLByteString (fromChunksIO) import Streamly.Internal.FileSystem.File as StreamlyInternalFile (chunkReaderWith) import Streamly.Internal.System.IO (arrayPayloadSize)-import System.Directory (doesDirectoryExist,doesFileExist,getDirectoryContents)+import System.Directory (createDirectoryIfMissing,doesDirectoryExist,doesFileExist,getDirectoryContents)+import System.FilePath ((</>), (<.>), takeDirectory)+import qualified System.FilePath as F import System.PosixCompat.Files (getFileStatus,modificationTime) import System.Posix.Types (EpochTime) import WaiAppStatic.Storage.Filesystem (ETagLookup)@@ -114,7 +125,7 @@ -> Int -- ^ buffer size -> Q [Dec] mkStaticFilesListStreamly' fp fs makeHash size =- concat `fmap` mapM mkRoute fs+ Prelude.concat `fmap` mapM mkRoute fs where replace' c | 'A' <= c && c <= 'Z' = c@@ -221,7 +232,7 @@ files'' <- mapM dedupe files' dirs <- liftIO $ filterM (doesDirectoryExist . fullPath) allContents dirs' <- mapM (\f -> go (fullPath f) (front . (:) f)) dirs- return $ concat $ files'' : dirs'+ return $ Prelude.concat $ files'' : dirs' -- Reuse data buffers for identical strings dedupe :: [String]@@ -248,12 +259,151 @@ append a b = a ++ '/' : b -- | A replacement of+-- [CombineType](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/src/Yesod.Static.html#CombineType).+data CombineTypeStreamly = JS | CSS++-- | A replacement of+-- [CombineSettings](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html#t:CombineSettings).+data CombineSettingsStreamly = CombineSettingsStreamly+ { csStaticDir :: FilePath+ -- ^ File path containing static files.+ --+ -- Default: static+ --+ -- Since 1.2.0+ , csCssPostProcess :: [FilePath] -> L.ByteString -> IO L.ByteString+ -- ^ Post processing to be performed on CSS files.+ --+ -- Default: Pass-through.+ --+ -- Since 1.2.0+ , csJsPostProcess :: [FilePath] -> L.ByteString -> IO L.ByteString+ -- ^ Post processing to be performed on Javascript files.+ --+ -- Default: Pass-through.+ --+ -- Since 1.2.0+ , csCssPreProcess :: TL.Text -> IO TL.Text+ -- ^ Pre-processing to be performed on CSS files.+ --+ -- Default: convert all occurences of /static/ to ../+ --+ -- Since 1.2.0+ , csJsPreProcess :: TL.Text -> IO TL.Text+ -- ^ Pre-processing to be performed on Javascript files.+ --+ -- Default: Pass-through.+ --+ -- Since 1.2.0+ , csCombinedFolder :: FilePath+ -- ^ Subfolder to put combined files into.+ --+ -- Default: combined+ --+ -- Since 1.2.0+ }++instance Default CombineSettingsStreamly where+ def = CombineSettingsStreamly+ { csStaticDir = "static"+ {- Disabled due to: https://github.com/yesodweb/yesod/issues/623+ , csCssPostProcess = \fps ->+ either (error . (errorIntro fps)) (return . TLE.encodeUtf8)+ . flip luciusRTMinified []+ . TLE.decodeUtf8+ -}+ , csCssPostProcess = const return+ , csJsPostProcess = const return+ -- FIXME The following borders on a hack. With combining of files,+ -- the final location of the CSS is no longer fixed, so relative+ -- references will break. Instead, we switched to using /static/+ -- absolute references. However, when served from a separate domain+ -- name, this will break too. The solution is that, during+ -- development, we keep /static/, and in the combining phase, we+ -- replace /static with a relative reference to the parent folder.+ , csCssPreProcess =+ return+ . TL.replace "'/static/" "'../"+ . TL.replace "\"/static/" "\"../"+ , csJsPreProcess = return+ , csCombinedFolder = "combined"+ }++-- | A replacement of+-- [liftRoutes](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/src/Yesod.Static.html#liftRoutes).+liftRoutesStreamly :: [Route Static]+ -> Q Exp+liftRoutesStreamly =+ fmap ListE . mapM go+ where+ go :: Route Static -> Q Exp+ go (StaticRoute x y) = [|StaticRoute $(liftTexts x) $(liftPairs y)|]++ liftTexts = fmap ListE . mapM liftT+ liftT t = [|pack $(TH.lift $ T.unpack t)|]++ liftPairs = fmap ListE . mapM liftPair+ liftPair (x, y) = [|($(liftT x), $(liftT y))|]++-- | A replacement of+-- [combineStatics'](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/src/Yesod.Static.html#combineStatics').+combineStaticsStreamly' :: CombineTypeStreamly+ -> CombineSettingsStreamly+ -> [Route Static] -- ^ files to combine+ -> Int + -> Q Exp+combineStaticsStreamly' combineType CombineSettingsStreamly {..} routes size = do+ texts <- qRunIO $ mapM (\fp -> do let lazyfile = S.unfold StreamlyInternalFile.chunkReaderWith (arrayPayloadSize (size * 1024),fp)+ lazybs <- liftIO $ StreamlyLByteString.fromChunksIO lazyfile+ return lazybs+ )+ fps+ let textss = L.concat texts+ let textsss = TLE.decodeUtf8 textss+ ltext <- qRunIO $ preProcess textsss+ bs <- qRunIO $ postProcess fps $ TLE.encodeUtf8 ltext+ let hash' = base64md5Streamly bs+ suffix = csCombinedFolder </> hash' <.> extension+ fp = csStaticDir </> suffix+ qRunIO $ do+ createDirectoryIfMissing True $ takeDirectory fp+ L.writeFile fp bs+ let pieces = map T.unpack $ T.splitOn "/" $ T.pack suffix+ [|StaticRoute (map pack pieces) []|]+ where+ fps :: [FilePath]+ fps = map toFP routes+ toFP (StaticRoute pieces _) = csStaticDir </> F.joinPath (map T.unpack pieces)+ postProcess =+ case combineType of+ JS -> csJsPostProcess+ CSS -> csCssPostProcess+ preProcess =+ case combineType of+ JS -> csJsPreProcess+ CSS -> csCssPreProcess+ extension =+ case combineType of+ JS -> "js"+ CSS -> "css"++-- | A replacement of -- [base64md5File](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/src/Yesod.Static.html#base64md5File). base64md5FileStreamly :: FilePath -> Int -> IO String base64md5FileStreamly fp size = fmap (base64Streamly . encode) $ (hashFileStreamly fp size) where encode d = ByteArray.convert (d :: Crypto.Hash.Digest MD5)++-- | A replacement of+-- [base64md5](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/Yesod-Static.html#v:base64md5).+base64md5Streamly :: L.ByteString+ -> String+base64md5Streamly lbs = do+ let hashedlbs = hashlazy lbs+ base64Streamly $ encode hashedlbs+ where+ encode d = ByteArray.convert (d :: Digest MD5) -- | A replacement of -- [base64](https://hackage.haskell.org/package/yesod-static-1.6.1.0/docs/src/Yesod.Static.html#base64).
yesod-static-streamly.cabal view
@@ -20,7 +20,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.1.4.5+version: 0.1.5.1 -- A short (one-line) description of the package. synopsis: A streamly-based library providing performance-focused alternatives for functionality found in yesod-static.@@ -83,6 +83,7 @@ bytestring >= 0.11.4 && < 0.12, containers >= 0.6.7 && < 0.7, cryptonite >= 0.30 && < 0.31,+ data-default >= 0.7.1 && < 0.8, directory >= 1.3.7 && < 1.4, filepath >= 1.4.2 && < 1.5, memory >= 0.18.0 && < 0.19,