shadow-cljs

itaied 2025-07-02T13:07:11.918169Z

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)]
                    ...}

thheller 2025-07-02T13:12:31.894639Z

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

thheller 2025-07-02T13:13:03.775549Z

if you follow that workflow the repl/start function would be the ideal place to just call that supposed hook

itaied 2025-07-02T13:17:06.343479Z

👍

itaied 2025-07-02T15:05:41.114369Z

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)

thheller 2025-07-02T15:39:03.687189Z

quite honestly .. do it by hand

f2wHTttf 2025-07-02T22:22:14.173229Z

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.

thheller 2025-07-03T04:28:42.941669Z

maybe use the approach I use in https://github.com/thheller/shadow-css?

thheller 2025-07-03T04:29:31.716829Z

as in the css macro is entirely static and then to build just gets processed externally without evaluation. basically as edn?

thheller 2025-07-03T04:30:22.718999Z

thats the function that takes the raw source string and finds (css ...) forms

thheller 2025-07-03T04:31:19.855039Z

the css macro itself just expands to a deterministic class name that both sides can construct

thheller 2025-07-03T04:32:09.360799Z

as for transforming the compiler cache and stuff that is not possible

thheller 2025-07-03T04:32:38.910009Z

in theory the macro could do everything and then write something into the compiler cache that a compiler hook extracts

thheller 2025-07-03T04:32:56.292779Z

but the hook runs too late, so it can only read what already exists

f2wHTttf 2025-07-03T05:35:23.766959Z

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).

f2wHTttf 2025-07-03T05:36:50.574899Z

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.

thheller 2025-07-03T05:37:16.979689Z

the cache is accessible. its just read only

👌 1
thheller 2025-07-03T05:38:01.623169Z

well you can modify it, but it just doesn't do anything since the compilation is already done

f2wHTttf 2025-07-03T05:50:30.469919Z

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).

thheller 2025-07-03T06:06:41.371109Z

well clojure code can run whenever

thheller 2025-07-03T06:07:12.364969Z

not exactly sure what problem you are trying to solve, so I'm only guessing for all of this

f2wHTttf 2025-07-03T06:11:14.937239Z

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).

thheller 2025-07-03T06:13:07.201429Z

so, if its clojure code a macro can do that. shadow-cljs doesn't even need to know anything about it.

👌 1
thheller 2025-07-04T07:29:09.234759Z

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

thheller 2025-07-04T07:30:21.306329Z

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

thheller 2025-07-04T07:31:03.708189Z

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 😛

thheller 2025-07-04T07:31:54.301349Z

like iterate all namespaces, find all vars tagged with :export-css and write files accordingly

thheller 2025-07-04T07:32:35.311969Z

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?

f2wHTttf 2025-07-04T16:57:58.934869Z

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.

f2wHTttf 2025-07-04T17:06:38.991439Z

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.

thheller 2025-07-04T17:39:48.199529Z

hence making a clojure function that does exactly what you want per build target

f2wHTttf 2025-07-04T17:48:11.876679Z

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).

f2wHTttf 2025-07-04T18:45:56.144879Z

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).

f2wHTttf 2025-07-03T17:18:42.798339Z

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?

thheller 2025-07-03T17:19:39.748109Z

none of that makes sense sorry 😛 why is vite suddenly in the picture? thought you are using garden?

thheller 2025-07-03T17:20:19.897559Z

and no, metadata is not emitting into the JS output

f2wHTttf 2025-07-03T17:20:27.989829Z

ah rip, that's dead in the water then

thheller 2025-07-03T17:20:53.075449Z

if you care to explain in detail the problem you are trying to solve I can maybe make suggestions

thheller 2025-07-03T17:21:09.672879Z

but do not include any info about how you currently think about solving it, that is just distracting

thheller 2025-07-03T17:21:29.899589Z

what is the macro generating, with a real example, what do you need instead?

f2wHTttf 2025-07-03T17:26:38.656409Z

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.

f2wHTttf 2025-07-03T17:28:44.380049Z

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.

thheller 2025-07-03T17:29:49.304409Z

that sounds like a horrible idea to be honest

thheller 2025-07-03T17:30:20.900279Z

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

thheller 2025-07-03T17:33:09.927009Z

shrink the problem description some more. with this I can make no suggestions, far too many unknowns on my end

f2wHTttf 2025-07-03T17:40:07.887439Z

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).

f2wHTttf 2025-07-03T22:29:32.156719Z

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.