when do build-hooks execute when in watch mode?
i want to execute a clj code (generate a requires file)
i want this function to run on every build (and probably for every change in the code)
(defn generate-requires [state]
(let [ns-name 'ui-framework.presets.requires
dir "src/ui_framework/presets"
output (str dir "/requires.cljs")]
(export-deps dir ns-name output))
state)
and
:app-runner-esm {:target :esm
:js-options {:js-provider :import}
:build-hooks [(build/generate-requires)]
...}as per my general answer: this should absolutely not be a build hook. if it emits a file the build is supposed to see it cannot be a build hook
https://code.thheller.com/blog/shadow-cljs/2024/10/18/fullstack-cljs-workflow-with-shadow-cljs.html
if you follow that workflow the repl/start function would be the ideal place to just call that supposed hook
👍
maybe there's another simpler solution for this problem:
i want to load all of the files under a certain namespace (`app.components`)
so i'm generating a file (ns ... (:require [...] [...])) by reading the directory path
what are the ways to do so? considering i want a dev mode (with watch)
quite honestly .. do it by hand
Anyone have an example of a build hook which hooks into the compiler cache and transforms a Clojure value to some other output format?
Hoping to have a build hook that can export Clojure values as static CSS which works with shadow-cljs watch. Something like :target :esm-files but for CSS (or other arbitrary data formats) instead.
maybe use the approach I use in https://github.com/thheller/shadow-css?
as in the css macro is entirely static and then to build just gets processed externally without evaluation. basically as edn?
https://github.com/thheller/shadow-css/blob/main/src/main/shadow/css/analyzer.cljc#L345
thats the function that takes the raw source string and finds (css ...) forms
the css macro itself just expands to a deterministic class name that both sides can construct
as for transforming the compiler cache and stuff that is not possible
in theory the macro could do everything and then write something into the compiler cache that a compiler hook extracts
but the hook runs too late, so it can only read what already exists
Unfortunately the CSS isn't entirely static as I'm using some math functions (e.g. exponential) to get type and space scales.
I have an existing hook which can do the eval already, but it's not suitable for watch mode since it'll recompute 50+ CSS style defs (there will be much more).
Since the compiler cache isn't accessible writable, it sounds like I'd have to track the cache state externally (e.g. file on disk) though that's probably more trouble than it's worth.
the cache is accessible. its just read only
well you can modify it, but it just doesn't do anything since the compilation is already done
What if the hook is for the :compile-prepare phase?
The existing hook is able to eval since it's executing Clojure JVM code in the source file (they're .cljc files).
well clojure code can run whenever
not exactly sure what problem you are trying to solve, so I'm only guessing for all of this
Incrementally generating CSS from Clojure code for watch mode. If the symbol with the CSS def changes, regenerate the resulting file for the namespace (and only this namespace, no other namespaces).
so, if its clojure code a macro can do that. shadow-cljs doesn't even need to know anything about it.
so, I still don't get any of this, but why do you insist so hard on this being solved via CLJS? why not just add a (write-css-to-file) function/macro call at the bottom of this ns
probably wrapped in #?(:clj ...) if .cljc. that function call can do whatever it want, its just clojure code, so it could write the css in whatever shape you want
its a side effect of loading the ns, so not a big fan personally, so instead I'd write a function that inspects the runtime instead. in clojure you can just do that no problem 😛
like iterate all namespaces, find all vars tagged with :export-css and write files accordingly
from your description it seems like shadow-cljs is basically only generating the css anyway? so why have it at all if you can do it all in clojure and the rest in squint/cherry?
It's to avoid having a side-effecting macro so the consumer can decide what to do with the Clojure value instead of a baked-in heuristic that tries to figure out when to do side effects.
Not having that macro baked into the namespace helps deal with different consuming toolchains. For example, web isn't the only platform you can target with HTML + CSS + JS since there's desktop platforms (e.g. GTK for Linux) that likewise use it. The build toolchain, however, might be completely different (e.g. web frontends typically use Vite nowadays, GTK stuff is primarily meson based) which may change how the CSS Clojure values should be consumed.
hence making a clojure function that does exactly what you want per build target
Yup.
I didn't see some build-time CSS-in-JS solution that would let me pass the Clojure CSS string to create a static stylesheet, so that's why these cursed import hacks exist if I want something incremental instead of the all-at-once Clojure to CSS extraction which my existing shadow-cljs build hook already does (too expensive for watch mode).
from your description it seems like shadow-cljs is basically only generating the css anyway?Not quite. I have a gradient function which I want to both:
1. Use at build time to generate static CSS (e.g. generate CSS variables for --blue-100 up to --blue-900 for a 9-step gradient).
2. Use at run time to generate dynamic inline CSS (e.g. data visualizations that have runtime dynamism requiring on-the-fly gradient calculations).
Hmm maybe a way around this is to have this:
• A ui-component.css namespace which :target :esm-files turns into a ui-component.css.js file.
◦ Any Clojure values that need to be exported as CSS have the ^:export-css meta attached.
• A vite-plugin-css-in-cljs namespace which :target :esm-files turns into a vite-plugin-css-in-cljs.js file that behaves as a Vite plugin.
◦ The plugin will act on any import '{prefix}.css.js' statements in JS code and create a virtual CSS file named {prefix}.css.
▪︎ Ideally this hooks into https://vite.dev/guide/features.html#css to get things like scoped styles. Need to check if it works.
◦ This file will use (meta {CLJS-value-as-JS?}) to figure out if a given JS module member needs to be turned into CSS.
◦ All of the exportable values get concatenated into a single CSS file string and returned.
This avoids the macro which feels like it'll complect both the export decision and its configuration (e.g. same style as having the ^:export meta and the export config in shadow-cljs.edn).
This leaves 2 open questions:
1. Is CLJS metadata preserved when transformed to JS and back?
2. Does Vite subject virtual files to the rest of its plugin chain?
none of that makes sense sorry 😛 why is vite suddenly in the picture? thought you are using garden?
and no, metadata is not emitting into the JS output
ah rip, that's dead in the water then
if you care to explain in detail the problem you are trying to solve I can maybe make suggestions
but do not include any info about how you currently think about solving it, that is just distracting
what is the macro generating, with a real example, what do you need instead?
This is a setup for frontends not using React (so no UIx or Reagent). The current targets are Solid.js (wants .jsx files), Vue (wants .vue files), and Svelte (wants .svelte files). Lets just focus on Solid.js for now.
For the parts interacting with Solid.js directly, I'm using #cherry or #squint (haven't decided on which, but both should work). These dialects have the #jsx literal which will output .jsx files directly for Solid.js's Vite build plugin to consume and run compile on.
The issue is, the core logic and CSS are both using #emmy to do some complicated math. Cherry and Squint don't work with Emmy yet since they're still missing a bunch of features (e.g. requiring user-defined namespaces).
So the workaround is to split build into a 2-stage process:
1. shadow-cljs compiles code that only works on CLJS proper (e.g. stuff depending on Emmy) and produces individual {namespace}.js files with :target :esm-files.
2. Cherry/Squint compile code that imports the shadow-cljs outputs and interacts with the frontend frameworks directly.
That on its own isn't very difficult since I already have it working (`shadow-cljs release main` into a vite build/`dev`).
The main problem is supporting incremental compile on the shadow-cljs side so I can use shadow-cljs watch + vite dev instead for live incremental updates.
that sounds like a horrible idea to be honest
but you skipped the problem entirely 😛 just introduced a new thing. I have no idea what emmy is, neither do I know much about cherry/squint
shrink the problem description some more. with this I can make no suggestions, far too many unknowns on my end
Oh it's absolutely terrible, but it's a necessary evil to split my CLJS code base into parts that work better on the CLJS compiler (many Clojure libs) and parts that work better with a Closure-less CLJS dialect like Cherry/Squint (certain Node.js libraries and frontend frameworks).
Asked on the Vite GitHub about a JS to CSS plugin for ref: https://github.com/vitejs/vite/discussions/20348 If that works, then there's a feasible way to get incremental recompiles working on both the CLJS/shadow-cljs and Vite halves of the build pipeline for Clojure outputs that need to be transformed to non-JS artifacts.