shadow-cljs

valerauko 2025-06-16T09:08:52.934669Z

Is there some helper in shadow-cljs for adding a hash to a filename? I'd want to add a hash to my shadow-css generated css file as well

thheller 2025-06-16T09:11:21.694419Z

I personally use a unique output dir instead of hashing actual filenames. :output-dir "public/js/v1.3" or so

valerauko 2025-06-16T09:25:45.324779Z

Thanks! I've been able to set the javascript compile up following that, and adding shadow.html/copy-file as a hook it replaced the filename in the html as intended.

valerauko 2025-06-16T09:26:24.456039Z

What I'd like to know is how to achieve the same with a shadow.css build Is there some helper to add the hash to the filename? How am I supposed to call the copy-file hook from the css build clj?

thheller 2025-06-16T09:28:47.480349Z

you don't need to call the copy-file hook at all ever

thheller 2025-06-16T09:29:04.108669Z

I trust that you can generate some html by yourself. so the question then becomes where do you get the data

valerauko 2025-06-16T09:29:21.235789Z

I have a static index.html

thheller 2025-06-16T09:29:53.427489Z

for the shadow-cljs part a manifest.edn is generated in the :output-dir, which you can (read-string (slurp "public/js/manifest.edn")) or so to get the data you need

thheller 2025-06-16T09:29:58.606689Z

even easier with a static file

thheller 2025-06-16T09:30:40.776339Z

there is absolutely zero reason for any of this to run as part of the shadow-cljs build/hook

thheller 2025-06-16T09:31:38.975039Z

I mean look at the code for the current hook, which I should have never created to be honest

thheller 2025-06-16T09:31:51.360429Z

its not doing much

valerauko 2025-06-16T09:32:11.409539Z

yeah but not having to write it myself is still a nonzero gain

valerauko 2025-06-16T09:32:22.616469Z

could you point me to where you inject the file hash into the filenames?

thheller 2025-06-16T09:33:12.401769Z

for that I suggest looking at the manifest.edn and printing it

thheller 2025-06-16T09:33:50.522489Z

you'll find a structure like [{:module-id :main, :name :main, :output-name "main.js", ...

thheller 2025-06-16T09:34:14.296619Z

:output-name in this case will contain the hashed name

valerauko 2025-06-16T09:34:15.264079Z

I'd need to inject the hash similarly into my shadow.css css file name as well so any pre-existing infra is helpful. I don't think my css build script would write into the manifest.edn or would it?

thheller 2025-06-16T09:35:17.647659Z

so the copy-file hook just replaces /js/foo.js with /js/foo.<hash>.js

thheller 2025-06-16T09:37:31.779409Z

shadow.css builds are by design a clojure function. you can modify it however you like

thheller 2025-06-16T09:37:46.784819Z

for example currently in your build you will have a write-outputs-to call

thheller 2025-06-16T09:38:49.949049Z

so you just write your own that injects a hash

thheller 2025-06-16T09:39:12.719059Z

there is currently nothing built-in for this

agorgl 2025-06-16T12:35:40.532769Z

I think I've found a problem on how :dev-http handles classpath files. I have a deps.edn with :paths ["src/clj" "resources" "target/classes"], in my shadow-cljs.edn I have :dev-http {8280 ["classpath:public"]} and in a build configuration I have :output-dir "target/classes/public/js". When my repo is in a clean state (the target/ directory doesn't exist yet) the dev-http server won't serve the target/classes/public directory that gets created after the server starts. If I kill the shadow-cljs server and start it again (now that the directory exists and is populated) everything works fine and dev-http server happily serves the resources in that directory

thheller 2025-06-16T12:47:12.565859Z

not really a shadow-cljs problem

thheller 2025-06-16T12:50:23.130799Z

classpath is a set of paths, so it is not possible for shadow-cljs to know that classpath:public was supposed to be target/classes/public, might as well have been src/main/public as far as its concerned

agorgl 2025-06-16T12:50:45.852509Z

you are right

agorgl 2025-06-16T12:51:20.062619Z

is it possible that dev-http server, looks at the available public directories in the classpath and populates a file search path for them only on startup?

thheller 2025-06-16T12:51:20.621279Z

FWIW just use target/classes/public instead of classpath:public

thheller 2025-06-16T12:52:46.529499Z

don't understand what you mean by "file search path". basically if a request comes in for say /index.html it'll look for public/index.html ANYWHERE on the classpath. in any dir plus any .jar

agorgl 2025-06-16T12:53:27.617099Z

So, this change works:

:dev-http
- {8280 ["classpath:public"]}
+ {8280 ["resources/public" "target/classes/public"]}

thheller 2025-06-16T12:53:28.958779Z

that lookup is entirely outside the control of shadow-cljs, it uses the standard JVM lookup mechanism

thheller 2025-06-16T12:54:42.083489Z

yes, for regular dirs shadow-cljs will create them when launching

agorgl 2025-06-16T12:54:45.261249Z

I'm just curious why the first one did not work because:

(-> (System/getProperty "java.class.path") (clojure.string/split #":") (->> (remove #(clojure.string/ends-with? % ".jar"))))
; --------------------------------------------------------------------------------
; State: clj
; eval (current-form): (-> (System/getProperty "java.class.path") (clojure.string/...
("dev/clj"
 "dev/cljs"
 "test/clj"
 "test/cljs"
 "src/cljs"
 "src/clj"
 "resources"
 "target/classes"

thheller 2025-06-16T12:55:25.404709Z

fairly certain the JVM just ignores any directory that doesn't exist and doesn't actually add them to the classpath loader thing

agorgl 2025-06-16T12:55:42.479619Z

nop, just confirmed this is not the case in #clojure

agorgl 2025-06-16T12:56:07.329549Z

or yeah it could be an internal jvm thing

thheller 2025-06-16T12:58:34.177379Z

clj -Sdeps '{:paths,["foo"]}'
Clojure 1.12.0
user=> (require '[clojure.java.io :as io])
nil
user=> (io/resource "foo.txt")
nil
user=> (def f (io/file "foo" "foo.txt"))
#'user/f
user=> (io/make-parents f)
true
user=> (spit f "foo")
nil
user=> (io/resource "foo.txt")
nil
user=>
clj -Sdeps '{:paths,["foo"]}'
Clojure 1.12.0
user=> (require '[clojure.java.io :as io])
nil
user=> (io/resource "foo.txt")
#object[java.net.URL 0x64711bf2 "file:/Users/thheller/code/tmp/foo/foo.txt"]

agorgl 2025-06-16T12:59:05.271689Z

til

agorgl 2025-06-16T13:00:20.925899Z

I'll change shadow-cljs :dev-http to use specific paths then, thank you!

thheller 2025-06-16T13:00:49.137439Z

well the correct solution would be to not use directories that don't exist as classpath entries šŸ˜›

thheller 2025-06-16T13:01:19.613269Z

it doesn't actually matter that target/classes/public doesn't exist. that will be created and used. it matters that target/classes doesn't exist

thheller 2025-06-16T13:02:41.972779Z

if it won't exists half the time it shouldn't be in :paths if you ask me

agorgl 2025-06-16T13:02:50.453829Z

yes I was thinking of creating the directory before starting the shadow-cljs server, but adding it to the dev-http paths is more elegant

agorgl 2025-06-16T13:03:10.127999Z

yes I was fairly conflicted about this too

thheller 2025-06-16T13:03:17.454119Z

for me its more a matter of correctness than elegance šŸ˜›

agorgl 2025-06-16T13:03:38.408159Z

on one hand I would love to have all 'build' state in a single dir like target/ so that repo cleanup is straightforward

agorgl 2025-06-16T13:05:17.680479Z

on the other hand i'm not sure how I should serve compiled/generated cljs sources from this directory that might not exist

agorgl 2025-06-16T13:05:51.121279Z

I think the answer is to remove the target/classes from the classpath, and use a dedicated output directory in target/ for cljs

agorgl 2025-06-16T13:06:05.965779Z

and when building the uberjar copy them to the resources target

Dustin Getz (Hyperfiddle) 2025-06-16T21:10:14.741699Z

i need to analyse typescript syntax and parse the import statements, anyone know of any clojure code that does this?

Roman Liutikov 2025-06-17T07:26:03.448659Z

If you prefer correctness, I’d call into Babel parser or some internal packages of TS to spit out AST as json

hkjels 2025-06-17T07:31:24.765249Z

I would also be surprised if there's no tree-sitter solution available, but if you just want the imports and don't really need proper analysis; you would be better of by just parsing the head yourself.

thheller 2025-06-17T08:23:27.506219Z

there should be plenty JS tools that already do this, for clojure/java I don't know of any