shadow-cljs 2025-10-03

If I run npx shadow-cljs run shadow.cljs.build-report client report.html in the reagent project, do I get a build report as produced with advanced CLJS? 🧡

βœ… 1

hey. completely missed this thread. seems like you sorted out the sizes? as for using lite mode you can just use deps.edn local root or git dep for cljs. that'll override the shadow-cljs preferred version. since there were no breaking changes that should be just fine. you should also be able to just set :compiler-options {:lite-mode true :elide-to-string true} for it to function.

I have not actually tested that, but see no reason why that wouldn't work.

I did try this and it failed with some mysterious errors. If I'm back at the kbd today I can try again

I mean a dev build will almost certainly not work, but release builds should be fine

I honestly don't quite see the point of lite-mode though. react alone is like 5 times of what is saved, so seems like a drop of water in a very big bucket πŸ˜‰

still curious about it though. will try to test myself when I find the time.

deps.edn:

{:paths ["src"]
 :aliases
 {:dev {:extra-paths ["test" "examples/todomvc/src" "examples/simple/src" "examples/geometry/src" "demo"]
        :extra-deps {org.clojure/clojurescript {:git/sha "f9a6856d91e45377391406fc34a581bc4043615e"
                                                :git/url ""}
                     doo/doo {:mvn/version "0.1.11"}
                     funcool/promesa {:mvn/version "11.0.678"}
                     thheller/shadow-cljs {:mvn/version "3.2.1"}}}}}

$ cat shadow-cljs.edn
{:source-paths ["src" "test" "examples/todomvc/src" "examples/simple/src" "examples/geometry/src" "demo"]
 :dev-http {8090 {:roots ["site/public" "target/shadow-cljs/client/public" "classpath:public"]}}
 :builds
 {:client {:target :browser
           :output-dir "target/shadow-cljs/client/public/js"
           :asset-path "/js"
           :modules {:main {:entries [reagentdemo.prod]}}
           :devtools {:ignore-warnings true}
           :compiler-options {:warnings {:fn-deprecated false}
                              }}
  :test {:target :karma
         :output-to "target/shadow-cljs/resources/public/js/karma.js"
         :output-dir "target/shadow-cljs/resources/public/js"
         :asset-path "js"
         :source-map true
         :compiler-options {:infer-externs :auto}
         :ns-regexp "(reagenttest\\.test.*|reagent\\..*-test)"}}
 :deps {:aliases [:dev]}
 #_#_:dependencies [[doo "0.1.11"]
                [funcool/promesa "11.0.678"]]}
npx shadow-cljs run shadow.cljs.build-report client report.html
https://gist.github.com/borkdude/ce742cb399d5a1d8afd7e98af810268c

seems I have to fix the source paths in deps.edn

hmm, those paths already exist in the alias so should be ok

hmm yeah weird. seems like that :jsdoc is incorrect

I have never bothered with type annotations for the closure compiler, so not sure what this means

shadow-cljs uses a newer closure compiler version that regular cljs, so maybe thats why its passing regular tests?

not sure I set anything in shadow-cljs that would affect this otherwise. I even opt out of some jsdoc related warnings

ah, goog-define already infers the type, so it just doubled. dunno why that extra jsdoc is there then https://github.com/clojure/clojurescript/blob/df04ee2c41ce7683b52c5793d0154ca88771f0f7/src/main/clojure/cljs/core.cljc#L758-L762

ah no I just remembered that I do in fact override the goog-define macro

IIRC I only added this because I wanted to update the closure compiler but CLJS was using an older incompatible version, so had to override. I guess this isn't needed anymore.

@borkdude Reagent shadow-cljs.edn is now updated so that the build from release command doesn't include the test suite, and matches the public site

πŸŽ‰ 1

small suggestions but since reagentdemo.main is only calling (core/init!) you might as well get rid of that one entirely and just use :modules {:main {:init-fn reagentdemo.core/init!}} in the build config

I'm still keeping cljsbuild configs around and this seems work for those also

πŸ‘ 1

There is also script to build the demo site with cljs.main + webpack: ./test-environments/bundle-adv/build.sh

❯ ls -l target/cljsbuild/prod-npm/public/js/main.js target/shadow-cljs-release/client/public/js/main.js target/bundle-adv/resources/public/js/main.js
-rw-rw-r-- 1 juho juho 551069 loka    6 15:57 target/bundle-adv/resources/public/js/main.js
-rw-rw-r-- 1 juho juho 547944 loka    6 15:58 target/cljsbuild/prod-npm/public/js/main.js
-rw-rw-r-- 1 juho juho 556878 loka    6 15:58 target/shadow-cljs-release/client/public/js/main.js

tiny differences between the artifact sizes with shadow-cljs, cljs.main+webpack, cljsbuild+npm-deps

And with lite?

I guess I can add a new build for that, based on the cljs.main version

if you want to tweak the size a bit you can set :compiler-options {:shadow-keywords true}. shaves off a few pct with no downsides

probably not too much. build doesn't seem to contain that many keywords

Hiccup does contain keywords though?

556878 -> 549788

yeah about what I expected. many different keywords is what I meant to say. that option has the most impact when lots of namespaced keywords are involved. don't see many in the code for that build

πŸ‘ 1

I'm getting pretty much the same artifact size for lite-mode + elide-to-string. Even a bit bigger output.

Ah hm the options isn't working correctly for some reason, I see both cljs.core.Vector cljs.core.PersistentVector uses.

Can you push a branch or something with lite mode? Perhaps we can show it to David to see if there is a bug in lite

I'm seeing + react-dom @ npm: 19.1.0 337.58 KB 36.2 % + org.clojure/clojurescript @ mvn: 1.12.42 304.06 KB 32.6 % + demo 116.61 KB 12.5 % + src 36.26 KB 3.9 % which looks a bit much?

is it possible to also see the gzipped sizes for individual packages? I guess not since zipping the whole is more efficient than the parts

Builds reports are generated for release builds, yes. But what do you mean by "a bit much"? As compared to what?

compared to numbers advertised elsewhere about react. I also noticed that the current reagent demo is 150kb gzipped. why has it grown with 100kb (gzipped)? not complaining, just trying to understand. context: https://mastodon.social/@borkdude/115310243716064865

Does it include both the minimized and debug file?

I don't know

when I do a release build, the size is the same

"+" on the row shows the invidual files

compared to numbers advertised elsewhere about react
Gotta know what's being used and done exactly to compare apples to apples. No actual idea but could be that the React is very amenable to tree shaking that GCC doesn't do. From the context: > React was 35K-40K gzipped Just tested - react-dom-client.production.js.gz from 19.2.0 is already 94 kB.

Closure could optimize (tree-shake) JS files, but that breaks many JS libs, so Shadow-cljs doesn't use that for npm deps.

Shadow-cljs also allows using webpack or other tools for tree-shaking

so if I would run this through esbuild with --minify it could get smaller

minified by esbuild: $ ls -lat /tmp/min.js -rw-r--r-- 1 borkdude wheel 947936 3 okt. 14:58 /tmp/min.js same size basically

No clue how React's react-dom is split into files, but files in v18 seem to be significantly smaller than in v19.

I wrote about using ESBuild a white back: https://www.metosin.fi/blog/2024-09-05-using-shadow-cljs-with-esbuild Though the shadow-cljs integration with :shadow.build/stage there is unnecessarily complex and there should be a better way.

I don't think you can just pass the shadow-cljs output to esbuild

at least if it is the regular browser output

there won't be the es6 module imports etc. there anymore for esbuild to process?

:js-provider :external works by finding the cljs ns require forms targetting npm libs -> shadow-cljs writes out a entry file for those libs and then you give just that entry file to esbuild

worth a shot indeed

FWIW, these are the files in the build report:

Source	Optimized
node_modules/react-dom/cjs/react-dom-client.production.js	167.43 KB
node_modules/react-dom/cjs/react-dom-server.browser.production.js	84.56 KB
node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js	81.51 KB
node_modules/react-dom/cjs/react-dom.production.js	3.3 KB
node_modules/react-dom/client.js	273
node_modules/react-dom/index.js	270
node_modules/react-dom/server.browser.js	245

those server files being included isn't normal

with js-provider: :main [JS: 581.51 KB] [GZIP: 139.43 KB]

when I then minify and bundle the file with esbuild: same size:

esbuild --bundle --minify target/shadow-cljs/client/public/js/main.js > /tmp/min.js

oh right you are looking at reagent demo site

that isn't very typical project

no wait I didn't release build yet, just the build report. wait a sec

yes, the demo site, since I was comparing with: https://chr15m.github.io/eucalypt/ which is of course not a fair comparison since eucalypt is just a cheap imitation of reagent

the normal entrypoint for reagent demo site also contains full reagent test suite

which also tests react-dom/server uses

which a normal app wont do

todomvc for example shows 170KB for react-dom

ok, with js-provider external I get 587473. same with --bundle - it seems esbuild isn't picking up. ah ok. what is the non-normal entry point for the reagent site or is it always built like this?

hmhm, it could be the site is always built like this

you can switch :modules {:main {:entries [http://reagentdemo.dev]}} -> :modules {:main {:entries [reagentdemo.prod]}}

nice! let me test

Might be it should be included in the config for release builds... maybe I have lost it accidentally at some point

much better: 361617

o wait, this was still with external providr

That's more like it:

Perhaps you are using the merge option at site deploy time

because this looks similar to what's currently deployed

deployed site doens't use shadow-cljs in fact

it is using the lein-cljsbuild version

that is why shadow-cljs config doesn't use that prod entrypoint

I see! I wanted to see the build report, this is why I looked at this

and it using clojurescript compiler npm-deps... which hmm, probably passes React code through Closure Compiler also

I have tested that React is one of JS libs that does seem to work after it goes through Closure Compiler

So the deployed artifact might be different size vs shadow-cljs

looks similar though, 150kb gzipped

compared to current "production" site

just ran a quick test locally bare bones uix app 303kb, 85kb gzipped same in reagent 335kb, 93kb gzipped

πŸ‘ 1

> I have tested that React is one of JS libs that does seem to work after it goes through Closure Compiler React's production builds distributed via NPM are in fact built with Closure Compiler

1

What Closure Compiler or other JS bundler could do, is to through out some exported functions that project doesn't use. Though that would also require that those functions are only users of some parts of React internals so those can be dropped. But Reagent anyone uses big part of the API so likely there isn't much that could be dropped.

πŸ‘ 1

Probably they would have split it up in multiple modules if you could save a lot of space, like they did with react-dom/server

I wonder if we can re-test this with CLJS master with lite mode and elide toString

Not sure how to do this with shadow from CLJS master in reagent’s shadow config

Cool, I posted the link in #cljs-dev