@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/iterableIf 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
file a bug, yeah the lite data structures don't do JS iterable yet - but trivial problem
your are using printing machinery - that's usually game over for lite-mode for the obvious reasons
in lite mode, printing is fine for dev - but for release artifact not a good idea
oh you mean (prn :init)?
yeah I can remove that :)
but you also need :elide-to-string
:elide-to-string true
tried this, it didn't help. output still 124kb https://github.com/borkdude/reagami/commit/a4e2403a46686bdce3b013e8e3a258bccca8c2cd
it's worth scanning the output w/ :pseudo-names true to see where you missed something about your implicit dependencies
👍
there is one dependency, the reagami.core namespace, but I can try to disable that one first
the namespaces matter less than some pattern you are using
that goes from 124kb -> 107kb
that reveals that there might be some issue w/ reagami but that's not neccessarily the only problem
I disabled reagami
I can bisect by commenting out stuff
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}))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))))))that's pretty much the repro
just commenting stuff out can be super misleading
like make an atom and swap it and it and js/console.log the output
if that doesn't result in a size change, then what you observe is a symptom but not the cause
also you eyeball the generated JS for these bits - so see anything suspicious
yeah this gives 60kb:
(def state (atom 0))
(swap! state inc)huh looking at Atom now
I didn't look at it much when I did the :lite-mode stuff
let me paste the pseudo-names output
in a gist
it's likely some protocol connectedness issue
https://gist.github.com/borkdude/dffcd5f5201fdac85561e8ecf46b387e
no, there really no such thing as protocol connectednes problems wrt DCE
the problem is doseq
for is also generally a problem - they both do too many things
the use of doseq in Atom is gratuitous
but Atom also depends on HashMap, also probably gratuitous
for the watches right?
yes
could just use a JS object perhaps
no, key has to be preserved
but a js/Map could work
but that's too new probably
so when you use lite-mode the standard lib isn't compiled itself with the copy on write stuff?
it is compiled to use lite-mode for literals and some simple constructors because that is generally sufficient
but that doesn't help when the standard lib relies on previously defined to build some later functionality
it's nice from a LISP-y point of view, but not so great for :lite-mode
note though atom is an interface so you could trivially avoid this one by providing your own atom w/o the problem
not sure about this one though, because too many expectations about atom.
for/doseq also not obvious, part of the reason they are so annoying is lack of information at compile about the type of thing
maybe just doseq -> run!
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
(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])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.
regular ClojureScript if you're not using external deps is not big, ~20K gizpped
the mental overhead of :lite-mode just isn't worth the savings.
only if you're doing something very specific, static websites, trivial scripting.
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
that's what I made squint for :)
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
perhaps a :warn-on-persistent-datastructure or so could help for :lite-mode
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.
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
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
still, atom , doseq for - worth pondering there might be a way.
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
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?
doseq and for both know about chunking - that was a big fix just getting rid of that
but for the single seq iteration case I haven't dug into why it's a code size pitfall.
it seems just calling first results into the same bloat the a single doseq gives:
(js/console.log (first []))I mean 50kb JS output, same with doseq
it's not first though, probably seq is sufficient
confirmed
(js/console.log (seq [])) here is 15K
need to be careful w/ your flags, I think?
(no compression here)
(js/console.log (atom {})) is 26K no compression
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/js50247 4 nov. 19:38 main.jshmm when I delete public/js first:
15424 4 nov. 19:39 main.js
That's weird!could it be it's re-using some old analysis which isn't valid anymore?
doseq now 11kb. that's better
this one's triggering 85kb:
(js/console.log (let [{:keys [a b]} {:a 1 :b 2}]
[a b]))triggered by:
(js/console.log (cljs.core/--destructure-map {}))ah right, that pulls in persistentarraymap
that's a bug though
what is?
destructure doesn't need to specify the map type
do you mean it could do this instead?
cljs.user=> (.createAsIfByAssoc (.-constructor {}) #js [1 2 3 4])
{1 2, 3 4}that's clever, though ObjMap/HashMap might be missing the needed static method here
code paths go through PAM or PHM w/o explicit request from user should be considered bugs
if you refer to PAM/PHM you'll pull in almost everything
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.
maybe it should be?
good point, fixed in master