Fork me on GitHub
#clojurescript
<
2021-02-18
>
martinklepsch11:02:26

I’m currently developing a new design system and am curious about people’s experiences with CSS-in-JS from a ClojureScript perspective. Have you tried it? What was good about it, what was bad?

dvingo12:02:15

I've been really enjoying using emotion from cljs. I made a small macro over the styled API: https://dvingo.github.io/cljs-emotion/#!/dv.cljs_emotion.devcards I recently added support for using other components as part of a CSS selector (bottom of this page): https://dvingo.github.io/cljs-emotion/#!/dv.cljs_emotion.target_styled and with that I have all the use-cases supported that I need to style pages and style complex components. A lot of features in the JS version require a babel compiler plugin, but we have macros which makes implementing certain features possible without that. You can use JS objects in your styles, so things like polished are supported https://polished.js.org too, and it supports SSR on node.js.

thheller12:02:50

I have used my solution https://github.com/thheller/shadow/wiki/shadow.markup for years but nowadays mostly use tailwind. It is ok and I prefer it over vanilla CSS but in my case naming every element gets tedious very quickly. tailwind isn't perfect either though so maybe some kind of inbetween would be good but I'm not sure how that would look.

dvingo12:02:32

I've noticed it gets annoying with having to name every component as well. emotion allows using the css prop https://emotion.sh/docs/css-prop for anonymous styles, but I haven't looked into translating this to cljs use yet.

martinklepsch13:02:32

@danvingo thanks for sharing that’s interesting! have you had a look at https://herb.roosta.sh/ as well?

dvingo13:02:52

np! I haven't looked at it yet, actually just heard about it, so don't have any strong opinions at this time

lilactown21:02:33

I've had great success with use emotion's css function rather than the styled components. I agree w/ thheller that I dislike tying the styles directly to a named component; instead we use a macro like:

(defcss header-styles
  {:font-weight :bolder
   :font-size "2rem"
   "&:hover" {:color "green"}})
etc.

lilactown21:02:27

and then you can refer to the header-styles class by var:

(d/div {:class header-styles} ,,,)

[:div {:class header-styles} ,,,]
and combine them with the cx function without messing around with complex inheritance issues:
(d/div {:class (cx header-styles some-other-style)} ,,,)

dvingo16:02:35

Nice, but you still have to come up with a name for the styles. Does this use @emotion/css or @emotion/react? And do you do SSR or is that not a use case for you?

dvingo18:02:52

Playing around with the anon-styles support - works well enough:

["@emotion/react" :refer [jsx]]
(jsx "div"
  (clj->js {:ref my-ref
            :css
                 {:position        "absolute"
                  :backgroundColor "white"
                  :width           "10em"
                  :height          (if is-open? height 0)
                  :overflow        "hidden"
                  :transition      (str "height 0.2s ease-in")}})
  (re/as-element
    [dropdown-menu {:role "menu"}
     [dropdown-item [:a {:href "/features"} "Features"]]
     [dropdown-item [:a {:href "/pricing"} "Pricing"]]
     [dropdown-item [:a {:href "/support"} "Support"]]
     [dropdown-item [:a {:href "/about"} "About"]]]))

lilactown18:02:55

we use @emotion/css. if you don't want to name the styles, you can use the css function:

(d/div {:class (css {:transition "height 0.2s eaase-in"})} ,,,)

dvingo19:02:42

ah, very cool, that makes sense

Baibhav Bista13:02:43

Hello everyone, I'm trying to replicate functionality similar to js/setInterval using cljs.core.async What I need is a regular tick (say every 100ms) This is what I've tried (go (while true       `(<! (timeout 100))`       `(some-function)))` The problem here is that since some-function takes a non-negligible amount of time, and so, time between ticks becomes more than 100ms Could anyone give me any pointers?

hkjels13:02:28

while true doesn’t do anything here, so you can just skip that I presume This will however run async, so if you want to use the result of some-function in the next iteration, you’re better of falling back to js timeouts

hkjels13:02:18

This should however run every 100 ms AFAICS

hkjels13:02:57

Out of curiosity, what heavy operation do you have to run every 100ms? Maybe the body of some-function can be optimized and/or moved to a worker

Baibhav Bista13:02:12

(timeout 100) would only give one value after 100ms and then close the channel, no? that was the rationale behind the (while true) bit

Baibhav Bista13:02:33

The function is actually not that heavy, I was just swapping an atom for a timer (was doing task 4 of https://eugenkiss.github.io/7guis/tasks/) I found that after some time, the value started to deviate from my watch time. Exaggerated the heaviness of the function in the question a bit 😅

henryw37413:02:41

maybe make some-function execute asynchronously? ie run it in setTimeout with 0 millis.

henryw37414:02:08

but it will always have the possibility to drift, because after the timeout elapses, there may be other stuff that the thread is executing

phronmophobic20:02:50

try:

(go (while true
      (<! (timeout 100))
      (go (some-function))))
This will immediately start the next timer rather than waiting for some-function to finish

phronmophobic20:02:24

to prevent drift over time, you can also recalculate the timeout's delay each loop:

(let [start-time (.now (js/Date.))]
  (go (loop [i 1]
        (let [delay (- (+ start-time (* i 100))
                       (.now (js/Date.)))]
         (<! (timeout delay)))
        (go (some-function))
        (recur i))))

Baibhav Bista00:02:28

thanks for the tips @U051B9FU1 @U7RJTCH6J I totally blanked on being able to run some-function inside its own go block! That does make sense 😁

👍 4
Baibhav Bista04:02:39

I ended up doing it like this first a tick channel which prevents drifts over time then later, use that tick channel with another function abstracted the drift-corrector-tick-channel since I expect that will come in handy for another thing too

(def tick-channel (chan))
(def tick-interval-ms 50)
(let [start-time (js/Date.now)]
  (go-loop [i 1]
    (let [delay (- (+ start-time (* i tick-interval-ms))
                   (js/Date.now))]
      (<! (timeout delay))
      (>! tick-channel i))
    (recur (inc i))))
(go-loop []
  (<! tick-channel)
  (some-function))

Baibhav Bista04:02:55

@U7RJTCH6J, if I am calculating delay like this anyways, would it be necessary to have some-function be in a separate go block?

jaihindhreddy05:02:58

@U0154QR3DC7 Doing regular ticks without drift involves a whole bunch of things, like performance.now() and document.timeline, and that's just with plain JS. Jake Archibald https://gist.github.com/jakearchibald/cb03f15670817001b1157e62a076fe95. And here's https://www.youtube.com/watch?v=MCi6AZMkxcU. This can be done through ClojureScript using JS interop. Hope this helps.

🙏 2
phronmophobic05:02:08

it depends on what some-function is. • will it always execute within the delay? • what if there's a bug in some-function at some point? • should multiple copies of some-function be allowed to run at once? if so, how many?

phronmophobic05:02:52

basically, it comes down to thinking about what might go wrong in the future as the code changes and planning how to fail gracefully if the best thing doesn't always happen. there's also a question of what happens if some-function throws an exception. should the repeating work continue? what if it keeps failing? etc.

👍 2
phronmophobic05:02:10

also. I never want to miss an opportunity to recommend reading http://erlang.org/download/armstrong_thesis_2003.pdf

🙏 2
Baibhav Bista10:02:31

@U7RJTCH6J Thanks, I'll give the thesis a read too 😄

Helins15:02:03

Is there a reason Clojure's BigInt notation hasn't been ported to CLJS? Mike Fikes did some work on this: https://gist.github.com/mfikes/9fc981ed7a190b8e9b2912eee98fdd5e

mfikes15:02:07

@adam678 I think there would still be a lot of work to do to ensure that nothing breaks (probably fairly challenging) and that there are no perf regressions. The above is really like the tip of the iceberg just showing that it might be possible. Also that stuff would only work with newer JavaScript engines, potentially creating a problem for the case where it doesn't exist.

Helins15:02:13

@mfikes Thanks, I was mostly curious. Backward compatibility is a good enough reason to be cautious

dnolen15:02:00

@adam678 performance really is a concern as well at least as important as backwards compatibility

👍 1
Helins18:02:32

@mfikes However, is there a way to emit the literal syntax? I get a syntax error when a macro outputs (js* "~{}n" n) Otherwise I can simply return a form calling BigInt with a string version of the input. Wouldn't make much difference I guess, would it?

mfikes18:02:11

Yeah, that's a good question. It seems you can't use js* as it emits parens around the number.