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
This commit is contained in:
Pagwin 2026-02-23 17:14:31 -05:00
parent 8082f95491
commit b3808f4136
No known key found for this signature in database
GPG key ID: 81137023740CA260
4 changed files with 62 additions and 18 deletions

View file

@ -29,7 +29,7 @@ library
hs-source-dirs: src 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 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 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 default-extensions: ApplicativeDo DataKinds NamedFieldPuns DerivingVia LambdaCase TypeApplications DeriveGeneric OverloadedRecordDot NamedFieldPuns DuplicateRecordFields DisambiguateRecordFields FlexibleInstances
test-suite test-markdown-parse test-suite test-markdown-parse

View file

@ -26,7 +26,7 @@ import Text.Megaparsec (errorBundlePretty)
import Text.Mustache (ToMustache (toMustache)) import Text.Mustache (ToMustache (toMustache))
import Types import Types
import Utilities.Action (getPublishedPosts, isDraft', markdownToHtml, markdownToPost, now, psbProgress) 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 qualified Utilities.CSS as CSS
import Utilities.FilePath (indexHtmlOutputPath, indexHtmlSourcePaths, isMarkdownPost, urlConvert) import Utilities.FilePath (indexHtmlOutputPath, indexHtmlSourcePaths, isMarkdownPost, urlConvert)
import qualified Utilities.Javascript as JS import qualified Utilities.Javascript as JS
@ -78,7 +78,6 @@ buildRules = do
postsRule postsRule
rss rss
bundled bundled
pure ()
-- css_resources -- css_resources
-- js_resources -- js_resources
@ -130,6 +129,8 @@ markdownPost src = do
post <- readMarkdownPost src post <- readMarkdownPost src
let rPost = fromPost post let rPost = fromPost post
postHtml <- applyTemplate "post.html" rPost postHtml <- applyTemplate "post.html" rPost
css_bundle <- Shake.askOracle CSS
js_bundle <- Shake.askOracle Javascript
time <- Utilities.Action.now time <- Utilities.Action.now
-- Shake.putInfo $ T.unpack $ urlConvert target -- Shake.putInfo $ T.unpack $ urlConvert target
@ -138,7 +139,9 @@ markdownPost src = do
{ pageTitle = rPostTitle rPost, { pageTitle = rPostTitle rPost,
pageContent = postHtml, pageContent = postHtml,
pageNow = time, 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 applyTemplateAndWrite "default.html" page target
@ -154,13 +157,17 @@ home =
let posts' = map fromPost posts let posts' = map fromPost posts
html <- applyTemplate "home.html" $ HM.singleton "posts" posts' html <- applyTemplate "home.html" $ HM.singleton "posts" posts'
time <- Utilities.Action.now time <- Utilities.Action.now
css_bundle <- Shake.askOracle CSS
js_bundle <- Shake.askOracle Javascript
-- Shake.putInfo $ T.unpack $ urlConvert target -- Shake.putInfo $ T.unpack $ urlConvert target
let page = let page =
Page Page
{ pageTitle = T.pack "Home", { pageTitle = T.pack "Home",
pageContent = html, pageContent = html,
pageNow = time, 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 applyTemplateAndWrite "default.html" page target

View file

@ -12,8 +12,10 @@ data Page = Page
pageContent :: Text, pageContent :: Text,
-- build time -- build time
pageNow :: Text, pageNow :: Text,
-- pageUrl :: Text,
pageUrl :: Text -- from Bundles
pageBundleCss :: [Text],
pageBundleJs :: [Text]
} }
deriving (Show, Generic) deriving (Show, Generic)
deriving (ToJSON) via PrefixedSnake "page" Page deriving (ToJSON) via PrefixedSnake "page" Page

View file

@ -1,13 +1,27 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
module Utilities.Bundling module Utilities.Bundling
( bundled, ( bundled,
BuildOracleVariant
( CSS,
Javascript
),
) )
where where
import Config (buildDir, cssGlobs, jsGlobs, outputDir) 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.Classes
import Development.Shake.FilePath ((</>)) import Development.Shake.FilePath ((</>))
import GHC.Generics (Generic) import GHC.Generics (Generic)
@ -32,10 +46,13 @@ resource_dir = outputDir </> "resources"
-- indicate completion/fulfill a need directive without rebuilding even when files -- 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 -- are left unchanged, maybe have the need be a $(filename).hash which we compute
-- ourselves based on the unminified input -- ourselves based on the unminified input
bundled :: Rules (BuildOracleVariant -> Action BuildOutputs) bundled :: Rules ()
bundled = addOracle $ \q -> case q of 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 CSS -> bundle_css
Javascript -> bundle_scripts Javascript -> bundle_scripts
pure ()
css_dir :: FilePath css_dir :: FilePath
css_dir = resource_dir </> "css" css_dir = resource_dir </> "css"
@ -52,21 +69,39 @@ css_esbuild_options =
"--metafile=" ++ css_meta_file "--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 -- need to take an input of resouces/blah.css
-- and in addition to bundling it -- and in addition to bundling it
bundle_css :: Action BuildOutputs bundle_css :: Action BuildOutputs
bundle_css = do bundle_css = do
need cssGlobs need cssGlobs
css_files <- getDirectoryFiles "" cssGlobs css_files <- getDirectoryFiles "" cssGlobs
cmd_ "esbuild" (generic_esbuild_options ++ css_esbuild_options ++ css_files) cmd_ ("esbuild" :: String) (generic_esbuild_options ++ css_esbuild_options ++ css_files)
pure $ error "TODO: pull the list of files from the meta file" metafile_outputs css_meta_file
-- Javascript and typescript -- Javascript and typescript
-- potentially: -- potentially:
-- "--target=es2020" -- "--target=es2020"
-- , "--format=esm" -- , "--format=esm"
js_esbuild_options :: [String] 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 :: FilePath
js_meta_file = buildDir </> "esbuild-js-meta.json" js_meta_file = buildDir </> "esbuild-js-meta.json"
@ -77,6 +112,6 @@ js_dir = resource_dir </> "js"
bundle_scripts :: Action BuildOutputs bundle_scripts :: Action BuildOutputs
bundle_scripts = do bundle_scripts = do
need jsGlobs need jsGlobs
js_files <- getDirectoryFiles "" cssGlobs js_files <- getDirectoryFiles "" jsGlobs
cmd_ "esbuild" (generic_esbuild_options ++ js_esbuild_options ++ js_files) cmd_ ("esbuild" :: String) (generic_esbuild_options ++ js_esbuild_options ++ js_files)
pure $ error "TODO: pull the list of files from the meta file" metafile_outputs js_meta_file