From b3808f4136774abe1478f9094919614e618619ce Mon Sep 17 00:00:00 2001 From: Pagwin Date: Mon, 23 Feb 2026 17:14:31 -0500 Subject: [PATCH] MVP Bundling is now implemented This commit gets bundling to the point where it can be used now TODO: - Get ESBuild Stdout/stderr to not pop up in psb output - figure out how to split off map files so I'm not shunting them into script tags --- psb.cabal | 2 +- src/Psb/Main.hs | 15 ++++++++--- src/Types.hs | 6 +++-- src/Utilities/Bundling.hs | 57 +++++++++++++++++++++++++++++++-------- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/psb.cabal b/psb.cabal index 856035e..e197e6a 100644 --- a/psb.cabal +++ b/psb.cabal @@ -29,7 +29,7 @@ library hs-source-dirs: src exposed-modules: Markdown HTML Logger IR Logger.Shake Psb.Main Utilities Utilities.FilePath Utilities.Action Utilities.Javascript Utilities.CSS Templates Types Config Utilities.Bundling other-modules: Utilities.Parsing - build-depends: base >=4.20 && < 4.21, mustache >=2.4.2, shake >= 0.19.8, deriving-aeson >= 0.2.9, aeson, text >= 2.1.2, time, unordered-containers, yaml, megaparsec >= 9.7.0, transformers >= 0.6.2, css-syntax >= 0.1.0.2 + build-depends: base >=4.20 && < 4.21, mustache >=2.4.2, shake >= 0.19.8, deriving-aeson >= 0.2.9, aeson, text >= 2.1.2, time, unordered-containers, yaml, megaparsec >= 9.7.0, transformers >= 0.6.2, bytestring default-extensions: ApplicativeDo DataKinds NamedFieldPuns DerivingVia LambdaCase TypeApplications DeriveGeneric OverloadedRecordDot NamedFieldPuns DuplicateRecordFields DisambiguateRecordFields FlexibleInstances test-suite test-markdown-parse diff --git a/src/Psb/Main.hs b/src/Psb/Main.hs index 6e56077..85f07cb 100644 --- a/src/Psb/Main.hs +++ b/src/Psb/Main.hs @@ -26,7 +26,7 @@ import Text.Megaparsec (errorBundlePretty) import Text.Mustache (ToMustache (toMustache)) import Types import Utilities.Action (getPublishedPosts, isDraft', markdownToHtml, markdownToPost, now, psbProgress) -import Utilities.Bundling (bundled) +import Utilities.Bundling (BuildOracleVariant (CSS, Javascript), bundled) import qualified Utilities.CSS as CSS import Utilities.FilePath (indexHtmlOutputPath, indexHtmlSourcePaths, isMarkdownPost, urlConvert) import qualified Utilities.Javascript as JS @@ -78,7 +78,6 @@ buildRules = do postsRule rss bundled - pure () -- css_resources -- js_resources @@ -130,6 +129,8 @@ markdownPost src = do post <- readMarkdownPost src let rPost = fromPost post postHtml <- applyTemplate "post.html" rPost + css_bundle <- Shake.askOracle CSS + js_bundle <- Shake.askOracle Javascript time <- Utilities.Action.now -- Shake.putInfo $ T.unpack $ urlConvert target @@ -138,7 +139,9 @@ markdownPost src = do { pageTitle = rPostTitle rPost, pageContent = postHtml, pageNow = time, - pageUrl = urlConvert target + pageUrl = urlConvert target, + pageBundleCss = map T.pack css_bundle, + pageBundleJs = map T.pack js_bundle } applyTemplateAndWrite "default.html" page target @@ -154,13 +157,17 @@ home = let posts' = map fromPost posts html <- applyTemplate "home.html" $ HM.singleton "posts" posts' time <- Utilities.Action.now + css_bundle <- Shake.askOracle CSS + js_bundle <- Shake.askOracle Javascript -- Shake.putInfo $ T.unpack $ urlConvert target let page = Page { pageTitle = T.pack "Home", pageContent = html, pageNow = time, - pageUrl = urlConvert target + pageUrl = urlConvert target, + pageBundleCss = map T.pack css_bundle, + pageBundleJs = map T.pack js_bundle } applyTemplateAndWrite "default.html" page target diff --git a/src/Types.hs b/src/Types.hs index 69e6680..bddc6da 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -12,8 +12,10 @@ data Page = Page pageContent :: Text, -- build time pageNow :: Text, - -- - pageUrl :: Text + pageUrl :: Text, + -- from Bundles + pageBundleCss :: [Text], + pageBundleJs :: [Text] } deriving (Show, Generic) deriving (ToJSON) via PrefixedSnake "page" Page diff --git a/src/Utilities/Bundling.hs b/src/Utilities/Bundling.hs index 2ad3a26..6436f3e 100644 --- a/src/Utilities/Bundling.hs +++ b/src/Utilities/Bundling.hs @@ -1,13 +1,27 @@ {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} module Utilities.Bundling ( bundled, + BuildOracleVariant + ( CSS, + Javascript + ), ) where import Config (buildDir, cssGlobs, jsGlobs, outputDir) -import Development.Shake (Action, RuleResult, Rules, addOracle, cmd_, command_, getDirectoryFiles, need, (%>)) +import Data.Aeson +import Data.Aeson (decode) +import Data.Aeson.Key (toText) +import qualified Data.Aeson.KeyMap as KM +import qualified Data.ByteString.Lazy as BL +import Data.Maybe (fromJust) +import Data.String (IsString (fromString)) +import Data.Text (Text) +import qualified Data.Text as T +import Development.Shake (Action, RuleResult, Rules, addOracle, addOracleCache, cmd_, command_, getDirectoryFiles, need, newCache, readFile', (%>)) import Development.Shake.Classes import Development.Shake.FilePath (()) import GHC.Generics (Generic) @@ -32,10 +46,13 @@ resource_dir = outputDir "resources" -- indicate completion/fulfill a need directive without rebuilding even when files -- are left unchanged, maybe have the need be a $(filename).hash which we compute -- ourselves based on the unminified input -bundled :: Rules (BuildOracleVariant -> Action BuildOutputs) -bundled = addOracle $ \q -> case q of - CSS -> bundle_css - Javascript -> bundle_scripts +bundled :: Rules () +bundled = do + -- TODO: Need to adjust this oracle to split out source maps from js and css files + oracle <- addOracleCache $ \q -> case q of + CSS -> bundle_css + Javascript -> bundle_scripts + pure () css_dir :: FilePath css_dir = resource_dir "css" @@ -52,21 +69,39 @@ css_esbuild_options = "--metafile=" ++ css_meta_file ] +newtype Metafile = Metafile + { outputs :: Object -- keys are the file paths + } + deriving (Show) + +instance FromJSON Metafile where + parseJSON = withObject "Metafile" $ \o -> + Metafile <$> o .: "outputs" + +outputPaths :: Metafile -> BuildOutputs +outputPaths = map (T.unpack . toText) . KM.keys . outputs + +metafile_outputs :: FilePath -> Action BuildOutputs +metafile_outputs metafile_path = do + src <- readFile' metafile_path + let intermediate = fromJust $ decode $ fromString src + pure $ outputPaths intermediate + -- need to take an input of resouces/blah.css -- and in addition to bundling it bundle_css :: Action BuildOutputs bundle_css = do need cssGlobs css_files <- getDirectoryFiles "" cssGlobs - cmd_ "esbuild" (generic_esbuild_options ++ css_esbuild_options ++ css_files) - pure $ error "TODO: pull the list of files from the meta file" + cmd_ ("esbuild" :: String) (generic_esbuild_options ++ css_esbuild_options ++ css_files) + metafile_outputs css_meta_file -- Javascript and typescript -- potentially: -- "--target=es2020" -- , "--format=esm" js_esbuild_options :: [String] -js_esbuild_options = ["--outdir=" ++ js_dir, "--splitting", "--metafile=" ++ js_meta_file] +js_esbuild_options = ["--outdir=" ++ js_dir, "--splitting", "--format=esm", "--metafile=" ++ js_meta_file] js_meta_file :: FilePath js_meta_file = buildDir "esbuild-js-meta.json" @@ -77,6 +112,6 @@ js_dir = resource_dir "js" bundle_scripts :: Action BuildOutputs bundle_scripts = do need jsGlobs - js_files <- getDirectoryFiles "" cssGlobs - cmd_ "esbuild" (generic_esbuild_options ++ js_esbuild_options ++ js_files) - pure $ error "TODO: pull the list of files from the meta file" + js_files <- getDirectoryFiles "" jsGlobs + cmd_ ("esbuild" :: String) (generic_esbuild_options ++ js_esbuild_options ++ js_files) + metafile_outputs js_meta_file