shadow-cljs 2025-06-16

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

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

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.

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?

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

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

I have a static index.html

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

even easier with a static file

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

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

its not doing much

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

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

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

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

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

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?

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

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

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

so you just write your own that injects a hash

there is currently nothing built-in for this

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

not really a shadow-cljs problem

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

you are right

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?

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

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

So, this change works:

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

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

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

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"

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

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

or yeah it could be an internal jvm thing

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"]

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

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

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

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

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

yes I was fairly conflicted about this too

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

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

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

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

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?

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

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.

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