Hi folks, iβve upgraded to latest shadow-cljs, and thereby also clojure and clojurescript to the latest. I get this now on circleCI, I was wondering what that means
[2025-05-12 14:50:45.831 - WARNING] :shadow.build.classpath/bad-jar-contents - {:jar-file "/home/circleci/.m2/repository/re-frame-utils/re-frame-utils/0.1.0/re-frame-utils-0.1.0.jar", :bad-prefix "out", :bad-count 67}it means the distributed jar contains contents it shouldn't contain, like compiled clojurescript core sources
won't hurt anything, so safe to ignore
thx!
(shadow-cljs now just ignores those, but long ago got confused)
@thheller have you ever looked into automatic code splitting in shadow? I want to explore it a bit more. From what I understand chunking is Closure's job, but looks like shadow is doing some preparations before feeding everything to Closure. I'm wondering what would be the easiest hacky way to prototype this.
I did experiment but couldn't find a solution I was happy with
as far as closure is concerned it needs a definition of chunks. as in which files are grouped together and chunks can depend on each other
basic graph theory stuff
how those chunks are defined and which files go where the closure compiler doesn't really care about
https://github.com/ChadKillingsworth/closure-calculate-chunks
I never looked into how that works, but might be a useful reference?
I could never figure out decent heuristics about when something should be in its own chunk or moved to different chunks
A depends on B, C uses like a 5% bit of A. the naive way just makes C depends on A, which is not ideal. neither is moving that bit to B. neither is creating an extra chunk. it all depends on manual configuration is the only way I found to deal with this
code splitting will always require the user changing code, so it'll never be fully automatic
problem is if you split too much you end up with many tiny chunks that individually are much larger than together (compression gets better with more files)
splitting always introduces an async aspect as well, which makes things icky in the user code
so in the end I found the manual :modules definition the most reliable and adjustable. user is in full control, but in turn makes things a bit more complicated
you can experiment with this from within shadow-cljs with a custom :target I guess
or I could give you a snippet that just uses the lower level shadow.build things
and just use the build state to produce a :modules map I guess
yeah I know auto splitting adds complexity, afaik in js world small chunks are merged together, thereβs a threshold
Custom target sounds good. Can you also send that snippet?
problem is you need to define the chunks before :advanced. so you don't really know the size its gonna be π
when I built the :npm-module target (one chunk per ns) I encountered some issues that basically disabled some :advanced things, so the end result was also larger than needed
:esm-files also has that issue, can even leave files completely empty because all the code is moved out of them
but I guess getting something equivalent to webpack shouldn't be that hard
(ns shadow.raw-build
(:require
[shadow.build.api :as build-api]
[shadow.build.npm :as npm]
[shadow.build.resolve :as res]
[shadow.cljs.devtools.server.util :as util]))
(let [build-state
(-> (util/new-build {:build-id :raw-test
:js-options {:js-provider :shadow}} :dev {})
(build-api/with-npm (npm/start {})))
[entries build-state]
(res/resolve-entries build-state '[foo.bar])
build-state
(build-api/compile-sources build-state entries)]
;; in build state
;; :sources holds all the info about the source files
;; :outputs holds all the output data, nothing is written to disk yet (besides cache)
;; [:compiler-env :cljs.analyzer/namespaces] holds all compiler metadata
;; figure out :modules now
)basically after compiler-sources you'd have all the data available you could possible need to figure out :modules
resolve-entries basically expects a vector of namespace symbols that should be compiled (+ their dependencies). resolve is what orders them also
:target is basically a higher level of abstraction for all this, but is doing all the underlying work
watch basically just runs this in a loop -> resolve, compile, flush, wait
configure is how build configs are normally processed using the :target stuff https://github.com/thheller/shadow-cljs/blob/084b63cdfbcbc72e52f573f60064e6acc3848700/src/main/shadow/build.clj#L314-L442
compile for the CLJS/JS compilation parts https://github.com/thheller/shadow-cljs/blob/084b63cdfbcbc72e52f573f60064e6acc3848700/src/main/shadow/build.clj#L503-L525
optimize for closure, etc
I think you'd need to compile one without modules to figure out what the sources actually do
then setup modules, maybe compile again, or just use the state that already has it compiled
dunno if that makes sense π
build-state is rather large. I built Inspect in the shadow-cljs to be able to look and browse it. so just (tap> build-state) in the snippet above (when run via shadow-cljs clj-repl or so)
printing it will for sure blow up your REPL π its like 200mb when printed
nice! all of this should be helpful, thank you
the thing I experimented with basically started with one entrypoint (regular ns), that or its dependencies would use a special macro which left some metadata about dynamic imports
so the thing would run in loop until no new things were found
basically how webpack does it