@lilactown await
would just make the surrounding function return a Promise and you would still have the same problem?
Could you just shim cljs.test/async
on the clj side or something:
(defmacro async [done & body] `@(do ~@body))
Maybe there should be a proper implementation in clojure.test
thoughHas anyone tried the http://hamsters.io library for multithreading js? Would you recommend it, or suggest alternatives?
Would it be recommended to be used in a e.g. react application to boost performance?
The only possible recommendation with such a vague description could only be to first measure what exactly degrades the performance. I.e. browser profiling. Only then you can start think of proper solutions.
Also, in browsers there's no backend-like multithreading. Regardless of what library you choose, it will use web workers. Web workers have a bunch of limitations so it's actually very likely that you will see performance degradation and not improvement when using such a library for some scenarios.
@ps I am skeptical it would make much of a difference
for curious, I've made compiler extension a while ago that adds async/await
specials https://github.com/roman01la/cljs-async-await
not something one would want to use of course, also doesn't remove potential breakage because of intermediate IIFEs generated by the compiler
I think there must be something I’m not understanding about the code splitting feature in cljs. I have two builds sharing much of the same code. After optimization, build A is 900k and build B is 800k. I introduced modules, and now mA is 80k, mB is 40k, but cljs_base is 1080k. How can the shared code (intersection of) A and B be larger than either?
Maybe closure isn’t able to do a proper tree shaking?
it may be larger because the module loader adds a bit of code you otherwise might not have used
You could have some code that is called once from A and once from B and can be inlined and then shrunken by optimizations in the separate builds https://www.cs.princeton.edu/~appel/papers/shrink.pdf
tree shaking and cross-module-montion largely depends on how the code is written
I see. Thanks for the quick responses!
Is there hope that I might see different/better results with shadow-cljs, @thheller, or is it the same underlying mechanism at play?
impossible to say without knowing what your code does. results will vary if the modules use different npm packages. if its purely CLJS code there likely won't be a big difference
but you will get build reports that will give you a much better picture about what is in your modules 😉 https://shadow-cljs.github.io/docs/UsersGuide.html#build-report
Now that looks interesting. I’ll give it a shot. Thanks!
@magnars no matter what you do have to build an understanding of how code splitting works to get the best results
it's not a magical thing - it works best if you have plan around your dependencies and you know where you don't want the heavy stuff to load
just turning it on isn't going to offer much
for example you have a React app, but you don't need that for login
then your login page <30K gzipped
in general if I'm building something simple, ClojureScript and basic Google Closure Library is all you need
and the payload is always around jQuery size
but this is the way to think about code splitting
what pages need lots of dependencies and which don't
Good points, for sure. I guess I was hoping it would be just a little magical. 🙃 What surprised me was that “extracting common code” would result in more code than either source.
the loader definitely brings in some machinery
code splitting for Google Closure stuff is very good
it will move properties and functions
but if your dependency graph wasn't planned for the split, nothing interesting is going to happen
so visual tools are nice here
but starting out with an actual plan is better - the results are quite obvious if you manage your deps and play around w/ it for a bit
you do have be careful w/ JS random libraries
all that can be done is coarse grained moves
so you're just pushing boulders around
Thanks, that’s helpful. 👍 It’s good to know that I can get better results by reorganizing my code to better align with how the code splitter works.
for Closure stuff and ClojureScript stuff it can broken apart, but you also have to realize common ClojureScript stuff is always gonna end up in the base
but even here tricks are possible
i.e. login page written ClojureScript but you take care to only use JS objects and arrays
then login page will be bytes
the hello-world compile test size we have in ClojureScript which seems artificial is useful for confirming that this style of planning can be done
Interesting!
I tried all this stuff when I originally integrated w/ the Closure code splitter
so it's not magical, but if you understand it - it can be wielded w/ surgical precision
I would not bother trying this stuff w/ JS tools, maybe I'm wrong
but the dep graphs make this type of thing too hard to reason about
Any reading material on the topic to suggest? I guess I can use thheller’s build report to find some culprits and experiment from there.
not much reading material
but I would say just play it with it for a couple of days
and do what I said above
try doing a login page w/ ClojureScript and no deps, see the output size
do an inner page w/ React etc.
not a real app but trivial examples that demonstrate it can be done
and after that it will be come quite obvious how to do it
Excellent. Thanks for taking the time to explain. Much appreciated.
bug reports welcome of course too 🙂
@magnars I see you commented on the loader regression, will take a look at that later
Thanks for the link! I’ll add it to the GitHub issue for those who wondered where to look (like me). 👍
To summarize what I understood today: Given two builds A and B, in a perfect world the size of (module A + base) = size of A, and the size of (module B + base) = size of B. However, we don’t have a perfect (magical) code splitter. To get a good split, the code needs to be organized (via namespaces) along the same lines as the modules. Better alignment yields better splits.
technically closure is able to split namespaces as well and move things wherever they are needed but certain things prevent that from working (eg. defmethod
can't be moved)
but yeah carefully designed code and namespaces split better in practice
yeah that maybe would help
I would need to detect whether I'm in a CLJS or CLJ context when emitting code, since macros are defined in Clojure. Or continue to split my CLJS and CLJ test files
Yes I thought that would be an issue but you can actually do (:require #?(:clj [my.async-shim :refer [async]])
in the tests
Hi There, I had a quick question, I am trying to disable a HTML button using ClojureScript
(def zoom-in-button (-> js/document (.getElementsByClassName "mapboxgl-ctrl-zoom-in")))
(if (= max-zoom (:zoom viewport))
((first zoom-in-button)(.setAttribute "disabled" "disabled")))
Trying to do
<button class="mapboxgl-ctrl-zoom-in" type="button" title="Zoom In" disabled =""></button>
But keep on getting this error Uncaught TypeError: "disable".setAttribute is not a function
In JavaScript
let ZoomIn = document.getElementsByClassName("mapboxgl-ctrl-zoom-in")[0];
ZoomIn.setAttribute("disabled","disabled")
((first zoom-in-button)(.setAttribute "disabled" "disabled")))
this is the equivalent JS:
ZoomIn[0]("disabled".setAttribute("disabled"))
do you see the problem?
Ahh it’s calling .setAttribute on array element :thinking_face: Am I right ?
no, it's calling .setAttribute
on the string "disabled"
, like your error message says
if we break it down piece by piece,
(.setAttribute "disabled" "disabled")
literally translates to JS:
"disabled".setAttribute("disabled")
which is throwing the error you see
you want to call .setAttribute
on the zoom-in-button, not the "disabled"
string
the .method
syntax in ClojureScript calls the method on the first argument
(.setAttribute x "disabled" "disabled")
I am kinda new in ClojureScript and trying to wrap my head around this
would you please give me little help how I can call on .setAttribute the element ?
CLJS:
(.setAttribute button "disabled" "disabled")
will be the same as JS:
button.setAttribute("disabled", "disabled")
does that help?Ohhh I get it Thank you @lilactown
yw!