Hello, I’m once again struggling with live-reload of css. Trying not to use live.js if possible. (to avoid the polling messing with my logging and events).
I have this for my generate-assets
defn generate-assets! [ctx]
(let [_ (gs.build/write-page-css!)]
;; Only proceed after write-page-css! has completed
(println "CSS generation complete, proceeding to HTML export")
(export-hiccup static-pages "target/resources/public")
(biff/delete-old-files
{:dir "target/resources/public"
:exts [".html"]})))
(defn on-save [ctx]
(biff/add-libs)
(biff/eval-files! ctx)
(generate-assets! ctx)
(test/run-all-tests #"gs.groundedsol.*-test"))
but when I save a page, and a reload starts, I get this in the log:
:reloading (gs.pages.consultation gs.build gs.groundedsol.home gs.groundedsol gs.example-test)
[qtp1566749353-38] INFO com.biffweb.impl.middleware - 100ms 200 get /consultationanddesign
[qtp1566749353-43] INFO com.biffweb.impl.middleware - 40ms 200 get /css/main.css?t=1743216721000
Wrote Compiled CSS to main.css
CSS generation complete, proceeding to HTML export
Testing clojure.core
Ran 0 tests containing 0 assertions.
0 failures, 0 errors.
It seems the CSS is written AFTER the pages are loaded. And indeed the css doesn’t visually refresh on reload, but it will if I manually reload (or the last reload’s css will update on the next reload, like it’s always a step behind).
How can I guarantee a page reload will use freshly compiled css and update the browser css accordingly?
I’m using biff/use-htmx-refresh module
I’m not using the native tailwind css build task. I’m using Ornament and Garden, so the css compilation might be a bit slow, but I dont see why it shouldnt work.I just took a stab at this--here's a working example: https://github.com/jacobobryant/biff/compare/master...better-htmx-refresh-example
Note the second commit is just copying the source code for use-htmx-refresh into your project so you can make changes to it. After that, the fix is in this commit: https://github.com/jacobobryant/biff/commit/71b205c41e91f311420be3f8c160eb262d3bf3da
i.e. instead of relying on use-htmx-refresh to actually send the refresh command, we send the command ourselves after assets have been generated.
This is working great, Thank you!! 🙏 I feel a little bit crazy when it’s 2025 and I cant get a html/css live-reload going, in an ecosystem thats oriented towards interactive programming 🥴 The fix was simple to implement and a testament to the flexibility of the framework. But as I was not understanding the (probably necessary) layers of indirection, I felt too helpless. I need to study and play with the core stuff more to not be limited by the minimal magic.
awesome! yeah, definitely recommend reading through the source code for the biff components--they're generally not too bad (`use-xtdb` might be the most complicated?) and will help a lot with understanding how things are being glued together.
I'll make a note to do something like this in the starter app so html/css refresh always works out of the box
Thanks for pointers and updates. When I am not experienced, every token of code seems probably precious and integral. And since the the tutorial and the updates are code diffs, which involve optionally restructuring and deleting code, it can feel a bit daunting or disorienting. But a good challenge I think. Almost like a stress test to push me beyond the veil of magic. But by the same token it really helps ease this discomfort when you are extra explicit and up to date in your documentation and examples.. Which you have been in general, and is a big asset of the Biff framework.
Another bug I imposed on myself was by moving the html head generation code to another ns, I broke the recaptcha loading, because the example code used ::recaptcha in the destructuring, and by moving the function, the implicit namespace changed. I was not used to using :: syntax, so I didnt see this obvious bug, and cost me some time. But i was forced to learn namespace aliasing syntax better and am a more resiliant programmer for it. So i think that fits with the ethos of the Biff too.
ah yeah, I've been hit by the :: thing too. Maybe I'll do something with that next time I'm working on that piece of the code.
Ran some claude on it and and is suggessed adding some middleware:
(defn wrap-regenerate-css-dev
"Regenerates CSS before serving HTML pages in development mode"
[handler]
(fn [{regenerate-css :gs.groundedsol/regenerate-css
:keys [uri] :as ctx}]
;; Only regenerate CSS for HTML page requests in dev mode
(when (and regenerate-css
(not (.endsWith uri ".css"))
(not (.endsWith uri ".js"))
(not (.endsWith uri ".png"))
(not (.endsWith uri ".jpg"))
(not (.endsWith uri ".ico")))
(println "Regenerating CSS for page request:" uri)
(gs.build/write-page-css!))
(handler ctx)))
This works to enforce the css updating on page reload. But now the css regenerates on every page reload from the browser, not just on file save.
But at least it works to see css changes 🙌Any insight into why the save->reload task is out of order, but this middleware enforces it?
I dont know what’s happing, between the htmx stuff, and the bundled code reloading mechanism. Or if it’s still a cache issue or a race condition, etc.
Ps I also added some more explicit cache busting helper stuff to css-gen fn via claude.
...
;; Write the CSS file
(spit css-path css-content)
;; Touch the file to ensure the last-modified timestamp is updated
(.setLastModified (java.io.File. css-path) (System/currentTimeMillis))
...
I think you need to switch the order of eval-files! and generate-assets! so that eval-files! comes second. because the htmx refresh thing gets triggered by eval-files!. probably I didn't notice the race condition before because I was using the tailwind watcher for css, so generate-assets! finished before the http request went through.
ideally things would be set up in a way so that you'd still do the eval files thing first, otherwise you might have stale code when the html / css gets generated. if you hit that problem, an easy hack would be to call eval-files! twice, before and after generate-assets!. for a more "correct" solution, you could look at the source for use-htmx-refresh and then rejigger things somehow so that eval-files! still runs first but the the refresh command doesn't run until after assets are generated.
Ok thanks, will try that and report back. So I really shouldnt need the extra middleware right?
correct, the middleware should be unnecessary
• refactor use-htmx-refresh > ◦ eval-files!
> ▪︎ runs first
> ▪︎ triggers “htxm-refresh-thing”
> ◦ generate assets
> ◦ refresh command
> ▪︎ runs after
I cant figure out how to do this.
I customized this fn for the components. But it doesnt seem to work.
(defn my-use-htmx-refresh
[{:keys [biff.refresh/enabled]
:as ctx}]
(if-not enabled
ctx
(let [_ (generate-assets! ctx)]
(-> ctx
(assoc :biff.refresh/clients (atom #{}))
(update :biff/handler htmx-refresh/wrap-htmx-refresh)
(update :biff.eval/on-eval conj #'htmx-refresh/send-refresh-command)))))
with this
(defn on-save [ctx]
(biff/add-libs)
(biff/eval-files! ctx)
(test/run-all-tests #"gs.groundedsol.*-test"))
Seems like use-htmx-refresh just updates the context, so I’m not sure where to put the render assets function.I tried adding eval-files! before and after generating css in the on-save fn. But then it reloads twice, and is too slow.