cljs-dev

borkdude 2025-11-04T13:34:26.287399Z

@dnolen I did a test with :lite-mode. Sadly I didn't get any smaller output with :optimizations :advanced but I did find one bug compared to non-lite:

(js/Set. ["checked" "disabled" "selected" "value"])
This works in non-lite but in lite you'll get an error about object not having Symbol/iterable

borkdude 2025-11-04T13:35:33.712659Z

If you want to test the project I was working with yourself: https://github.com/borkdude/reagami/tree/main/scratch/cljs-app

clj -M -m cljs.main --target browser --output-dir public/js --compile-opts '{:asset-path "js" :lite-mode true :optimizations :advanced}' --compile my-app.core

dnolen 2025-11-04T16:08:40.827259Z

file a bug, yeah the lite data structures don't do JS iterable yet - but trivial problem

dnolen 2025-11-04T16:09:59.530319Z

your are using printing machinery - that's usually game over for lite-mode for the obvious reasons

dnolen 2025-11-04T16:10:22.844629Z

in lite mode, printing is fine for dev - but for release artifact not a good idea

borkdude 2025-11-04T16:11:01.782519Z

oh you mean (prn :init)?

borkdude 2025-11-04T16:11:07.502949Z

yeah I can remove that :)

dnolen 2025-11-04T16:11:22.810949Z

but you also need :elide-to-string

dnolen 2025-11-04T16:12:05.926719Z

:elide-to-string true

borkdude 2025-11-04T16:14:57.459089Z

tried this, it didn't help. output still 124kb https://github.com/borkdude/reagami/commit/a4e2403a46686bdce3b013e8e3a258bccca8c2cd

dnolen 2025-11-04T16:16:54.862829Z

it's worth scanning the output w/ :pseudo-names true to see where you missed something about your implicit dependencies

borkdude 2025-11-04T16:16:59.639889Z

👍

borkdude 2025-11-04T16:17:23.448049Z

there is one dependency, the reagami.core namespace, but I can try to disable that one first

dnolen 2025-11-04T16:17:47.408459Z

the namespaces matter less than some pattern you are using

borkdude 2025-11-04T16:18:05.542829Z

that goes from 124kb -> 107kb

dnolen 2025-11-04T16:19:20.030259Z

that reveals that there might be some issue w/ reagami but that's not neccessarily the only problem

borkdude 2025-11-04T16:19:35.864689Z

I disabled reagami

borkdude 2025-11-04T16:21:35.840849Z

I can bisect by commenting out stuff

borkdude 2025-11-04T16:22:00.546699Z

this adds 60kb:

(defonce state
  (atom {:snake [[5 10] [4 10] [3 10]] ; initial snake body
         :dir [1 0] ; moving right
         :food [15 10] ; initial food
         :alive? true}))

borkdude 2025-11-04T16:23:07.502529Z

this adds another 45kb:

(.addEventListener js/window "keydown"
  (fn [e]
    (let [key (.-key e)]
      (swap! state update :dir
                         (fn [dir]
                           (case key
                             "ArrowUp" (if (= dir [0 1]) dir [0 -1])
                             "ArrowDown" (if (= dir [0 -1]) dir [0 1])
                             "ArrowLeft" (if (= dir [1 0]) dir [-1 0])
                             "ArrowRight" (if (= dir [-1 0]) dir [1 0])
                             dir))))))

borkdude 2025-11-04T16:23:58.102089Z

that's pretty much the repro

dnolen 2025-11-04T16:24:24.572599Z

just commenting stuff out can be super misleading

dnolen 2025-11-04T16:24:48.858059Z

like make an atom and swap it and it and js/console.log the output

dnolen 2025-11-04T16:25:36.632999Z

if that doesn't result in a size change, then what you observe is a symptom but not the cause

dnolen 2025-11-04T16:26:20.791149Z

also you eyeball the generated JS for these bits - so see anything suspicious

borkdude 2025-11-04T16:26:36.138559Z

yeah this gives 60kb:

(def state (atom 0))

(swap! state inc)

dnolen 2025-11-04T16:28:29.612949Z

huh looking at Atom now

dnolen 2025-11-04T16:28:48.889259Z

I didn't look at it much when I did the :lite-mode stuff

borkdude 2025-11-04T16:28:57.186409Z

let me paste the pseudo-names output

borkdude 2025-11-04T16:29:02.703159Z

in a gist

borkdude 2025-11-04T16:29:21.353899Z

it's likely some protocol connectedness issue

dnolen 2025-11-04T16:29:54.433909Z

no, there really no such thing as protocol connectednes problems wrt DCE

dnolen 2025-11-04T16:30:02.910859Z

the problem is doseq

dnolen 2025-11-04T16:30:18.896519Z

for is also generally a problem - they both do too many things

dnolen 2025-11-04T16:32:09.472279Z

the use of doseq in Atom is gratuitous

dnolen 2025-11-04T16:33:41.382809Z

but Atom also depends on HashMap, also probably gratuitous

borkdude 2025-11-04T16:33:54.532619Z

for the watches right?

dnolen 2025-11-04T16:33:57.141349Z

yes

borkdude 2025-11-04T16:34:35.270929Z

could just use a JS object perhaps

borkdude 2025-11-04T16:35:02.975699Z

no, key has to be preserved

borkdude 2025-11-04T16:35:07.829119Z

but a js/Map could work

borkdude 2025-11-04T16:35:13.768569Z

but that's too new probably

borkdude 2025-11-04T16:35:53.558809Z

so when you use lite-mode the standard lib isn't compiled itself with the copy on write stuff?

dnolen 2025-11-04T16:38:53.232229Z

it is compiled to use lite-mode for literals and some simple constructors because that is generally sufficient

dnolen 2025-11-04T16:39:33.404859Z

but that doesn't help when the standard lib relies on previously defined to build some later functionality

dnolen 2025-11-04T16:40:10.750019Z

it's nice from a LISP-y point of view, but not so great for :lite-mode

dnolen 2025-11-04T16:42:43.657349Z

note though atom is an interface so you could trivially avoid this one by providing your own atom w/o the problem

dnolen 2025-11-04T16:43:29.502709Z

https://clojure.atlassian.net/browse/CLJS-3458

dnolen 2025-11-04T16:44:06.352239Z

not sure about this one though, because too many expectations about atom.

dnolen 2025-11-04T16:44:37.735569Z

for/doseq also not obvious, part of the reason they are so annoying is lack of information at compile about the type of thing

dnolen 2025-11-04T16:47:36.826209Z

maybe just doseq -> run!

dnolen 2025-11-04T16:52:30.567259Z

https://clojure.atlassian.net/browse/CLJS-3459

borkdude 2025-11-04T16:59:46.621389Z

https://clojure.atlassian.net/browse/CLJS-3460

borkdude 2025-11-04T17:00:20.059019Z

I think doseq could be simplified if there's only one binding, which is very common, to emit run! instead but maybe that's a bit too hacky

borkdude 2025-11-04T17:03:53.873859Z

(doseq [i [1 2 3]]
  (doseq [j [4 5 6]]
    (prn [i j])))

=>

(run! (fn [i]
        (run! (fn [j] (prn [i j])) [4 5 6])) [1 2 3])

dnolen 2025-11-04T17:05:12.838469Z

I think so, :lite-mode is really kind of a tactical thing if you already know what you are doing. I think as you want to use more and more of the standard library it's going to be hard to take advantage of :lite-mode - diminishing returns.

dnolen 2025-11-04T17:05:57.369259Z

regular ClojureScript if you're not using external deps is not big, ~20K gizpped

dnolen 2025-11-04T17:06:19.229719Z

the mental overhead of :lite-mode just isn't worth the savings.

dnolen 2025-11-04T17:06:56.562339Z

only if you're doing something very specific, static websites, trivial scripting.

dnolen 2025-11-04T17:07:22.556869Z

one place where I think it might be useful that you can't really do right now is low level CLJS libs you would otherwise write in JS

borkdude 2025-11-04T17:07:41.636869Z

that's what I made squint for :)

borkdude 2025-11-04T17:08:25.078039Z

but it's not so hard to write low level JS in CLJS using aset etc. but it's kind of foot-gunny if you pull in doseq etc out of habit

borkdude 2025-11-04T17:09:00.986949Z

perhaps a :warn-on-persistent-datastructure or so could help for :lite-mode

dnolen 2025-11-04T17:09:07.480689Z

Yeah squint is great for that - but when doing this kind of work being able to get at GCL for some things is awesome - no other deps.

dnolen 2025-11-04T17:10:06.119769Z

At work I have a bunch of Light DOM web components, the output ~8-9K brotli - not very Clojure-y - but I don't have to write it in JS by hand, macros, yadda

borkdude 2025-11-04T17:11:12.892399Z

haha. I wrote reagami in "idiomatic" squint first, but ended up very imperative style CLJS to make it more performant. Totally not clojury, but if someone uses it you could just do Clojure style without noticing

dnolen 2025-11-04T17:11:53.429419Z

still, atom , doseq for - worth pondering there might be a way.

dnolen 2025-11-04T17:12:19.824019Z

for a while there I didn't think :lite-mode could even really work, so probably there are some clever routes here that don't break a bunch of expectations

borkdude 2025-11-04T17:14:54.725859Z

this is what doseq does in squint: https://squint-cljs.github.io/squint/?src=gzip%3AH4sIAAAAAAAAE9NIyS9OLVSIzlSINlQwUjCO5VKAAqvyjNQ8BY38lBR7hUxNkLhGQVGeQqamJgBptSxzNAAAAA%3D%3D What is the reason doseq pulls in too much stuff? It mostly works on first/rest etc without relying on persistent data structures directly?

dnolen 2025-11-04T17:18:00.206169Z

doseq and for both know about chunking - that was a big fix just getting rid of that

dnolen 2025-11-04T17:18:45.040779Z

but for the single seq iteration case I haven't dug into why it's a code size pitfall.

borkdude 2025-11-04T17:25:23.265009Z

it seems just calling first results into the same bloat the a single doseq gives:

(js/console.log (first []))

borkdude 2025-11-04T17:25:36.367339Z

I mean 50kb JS output, same with doseq

dnolen 2025-11-04T17:29:42.018649Z

it's not first though, probably seq is sufficient

borkdude 2025-11-04T17:30:09.175719Z

confirmed

dnolen 2025-11-04T17:42:19.810179Z

(js/console.log (seq [])) here is 15K

dnolen 2025-11-04T17:42:33.553829Z

need to be careful w/ your flags, I think?

dnolen 2025-11-04T17:42:47.512109Z

(no compression here)

dnolen 2025-11-04T17:44:23.247309Z

(js/console.log (atom {})) is 26K no compression

borkdude 2025-11-04T18:38:29.155019Z

these are my flags:

$ clj -M -m cljs.main --target browser --output-dir public/js --compile-opts '{:asset-path "js" :lite-mode true :elide-to-string true :optimizations :advanced}' --compile my-app.core  && ls -lat public/js

borkdude 2025-11-04T18:38:50.810849Z

50247  4 nov. 19:38 main.js

borkdude 2025-11-04T18:39:30.458009Z

hmm when I delete public/js first:

15424  4 nov. 19:39 main.js
That's weird!

borkdude 2025-11-04T18:39:46.782529Z

could it be it's re-using some old analysis which isn't valid anymore?

borkdude 2025-11-04T18:40:38.131289Z

doseq now 11kb. that's better

borkdude 2025-11-04T18:44:51.987999Z

this one's triggering 85kb:

(js/console.log (let [{:keys [a b]} {:a 1 :b 2}]
                  [a b]))

borkdude 2025-11-04T18:46:42.483409Z

triggered by:

(js/console.log (cljs.core/--destructure-map {}))

borkdude 2025-11-04T18:47:08.657359Z

ah right, that pulls in persistentarraymap

dnolen 2025-11-04T18:58:14.697369Z

that's a bug though

borkdude 2025-11-04T18:58:39.968079Z

what is?

dnolen 2025-11-04T18:59:04.873639Z

destructure doesn't need to specify the map type

borkdude 2025-11-04T19:00:14.115129Z

do you mean it could do this instead?

cljs.user=> (.createAsIfByAssoc (.-constructor {}) #js [1 2 3 4])
{1 2, 3 4}

dnolen 2025-11-04T19:01:10.999539Z

that's clever, though ObjMap/HashMap might be missing the needed static method here

dnolen 2025-11-04T19:01:51.554089Z

https://clojure.atlassian.net/browse/CLJS-3461

dnolen 2025-11-04T19:02:50.425689Z

code paths go through PAM or PHM w/o explicit request from user should be considered bugs

dnolen 2025-11-04T19:03:05.320359Z

if you refer to PAM/PHM you'll pull in almost everything

dnolen 2025-11-04T19:04:54.337759Z

re: the stuff from before, flipping :lite-mode isn't a cache busting compiler flag - so you probably just weren't observing anything at all.

borkdude 2025-11-04T19:05:20.550799Z

maybe it should be?

dnolen 2025-11-04T19:36:12.138509Z

good point, fixed in master