Skip to content

Ensure candidate extraction works as expected in Clojure/ClojureScript #17087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 13, 2025

Conversation

RobinMalfait
Copy link
Member

@RobinMalfait RobinMalfait commented Mar 9, 2025

This PR adds a Clojure/ClojureScript pre processor to make sure that candidate extraction works as expected.

Before After
image image

You can see that the classes preceded by : are not properly extracted in the before case, but they are in the after case. We do extract a few more cases now like :class and :className itself, but at least we also retrieve all the flex-* classes.

We could also always ignore :class and :className literals:
image

@chase-lambert
Copy link

I have noticed certain Clojurescript frameworks (well maybe just Fulcro) will use :className instead of just :class. I wonder if we could get that in these tests too. Anyways, my apologies for failing to let you know about that on Discord in our original discussion and I appreciate you doing this!

@RobinMalfait
Copy link
Member Author

RobinMalfait commented Mar 12, 2025

@chase-lambert Added :className for completeness, can you think of other structures in ClojureScript you would potentially write classes in?

@RobinMalfait
Copy link
Member Author

RobinMalfait commented Mar 12, 2025

For example, one thing I can think of is there a way to write normal text that is not inside of "…"?

In other pre processing steps for let's say Haml we can't assume that " and " are start and end characters of a string. But I feel like in ClojureScript you have to use them to write normal "text". Is that assumption correct?

@eneroth
Copy link

eneroth commented Mar 12, 2025

@RobinMalfait You assume correctly.

This is 6 symbols and a number

Clojure will complain that you're trying to refer to non-existing var "This" and so on.

"This is a string—no complaints"

How dependent is this strategy of classes being scoped within a :class/:className key + vector?

Will this work?

(let [some-classes [:bg-coal-50 :text-3xl]]
  ( {:class [:ring-0 some-classes]}))

@RobinMalfait
Copy link
Member Author

RobinMalfait commented Mar 12, 2025

@RobinMalfait You assume correctly.

This is 6 symbols and a number

Clojure will complain that you're trying to refer to non-existing var "This" and so on.

"This is a string—no complaints"

How dependent is this strategy of classes being scoped within a :class/:className key + vector?

Will this work?

(let [some-classes [:bg-coal-50 :text-3xl]]
  ( {:class [:ring-0 some-classes]}))

Perfect thanks! It's not dependent on :class/:className at all. We are not even trying to understand Clojure/ClojureScript here. The current extractor just requires valid boundary characters around classes such as 'flex' or with spaces like flex.

In this case, the : is not valid, so we replace it with as a pre processing step so we can find those classes. This is something we do internally in memory. Just to be clear we do not replace this on your actual files.

Concretely, this example you shared looks like this:

Today With this PR
image image

Small note: I changed bg-coal-50 to bg-red-50 just because it's a known default class. But bg-coal-50 would still work though 👍

But now you can see that the bg-red-50, text-3xl, and ring-0 are properly extracted.

@RobinMalfait RobinMalfait marked this pull request as ready for review March 12, 2025 13:30
@RobinMalfait RobinMalfait requested a review from a team as a code owner March 12, 2025 13:30
@philipp-spiess
Copy link
Member

philipp-spiess commented Mar 12, 2025

Let's make sure it works with quotes in comments:

;; This is not starting a string " <- HAHA!
(let [some-classes [:bg-coal-50 :text-3xl]]
  ( {:class [:ring-0 some-classes]}))

@RobinMalfait RobinMalfait merged commit 221855b into main Mar 13, 2025
5 checks passed
@RobinMalfait RobinMalfait deleted the fix/clojurescript branch March 13, 2025 10:31
@avocade
Copy link

avocade commented Apr 4, 2025

Thanks for this, finally clojure is a mainstream language!!! j/k 😅 Thanks for real, allows us to delete some hacky workarounds always nice.

@eneroth
Copy link

eneroth commented Apr 23, 2025

@philipp-spiess Tailwind 4 is not working entirely as expected for us. This seems to be related to classes with fractions in them, like :gap-x-1.5. Examples:

screenshot_2025-04-23_at_11 30 08_720

screenshot_2025-04-23_at_11 30 44_720

@philipp-spiess
Copy link
Member

@eneroth Hey! That looks very broken, indeed. Do you mind filing a new issue with a reproduction of what exactly isn't working?

@eneroth
Copy link

eneroth commented Apr 23, 2025

@philipp-spiess Before that, a thought occurs. Could it be that since you support eg. :div.flex-1.flex-2, things like :gap-1.5 is actually extracted to :gap-1 and 5?

@RobinMalfait
Copy link
Member Author

@eneroth we do extract gap-1 in that case indeed:
image

So now the question is, what is the difference between

:div.foo

and

:gap-1.5

Does Clojurescript have some hardcoded list of elements like div? And what about custom elements like my-custom-element where gap-1 could also be a custom element 🤔

I'm currently having a very hard time to even compile something basic like :div.foo. If you can point me in the right direction (or provide a minimal reproduction repo) that would be awesome.

That said, does :gap-1.5 actually compile to <div class="gap-1.5"></div>? Or does it compile to <div class="gap-1"></div> or something similar?

@eneroth
Copy link

eneroth commented Apr 23, 2025

@RobinMalfait I think this is the exact same reason why we ended up creating our own extraction plugin for v3, where I ended up having the same challenge. I settled on a compromise, which will get you to 99%: apart from splitting by ., also submit the entire thing for consideration.

This means that :gap-1.5 would result in ["gap-1" "5" "gap-1.5"]. It wouldn't handle eg. :gap-x-1.5.gap-y-2.5, but the :div.gap-x-1.5.gap-y-2.5 is syntax sugar which IMO should not be allowed to swallow any considerable amount of engineering hours, since it's always possible to rewrite it into something normal, ie. ($ :div {:class [:gap-x-1.5 :gap-y-2.5]} …)

For frameworks using the syntax sugar, I imagine that :div.gap-1.5 would indeed compile to <div class="gap-1 5"> or something nonsensical like this. One of the many reasons I'm not a fan of the dot-delimited sugar form. Explicit and distinct classes are always preferable IMO. Once you need a conditional, you'll have to extract the stuff from the dot-stack anyway.

I could see some possible logic of extracting the parts, eg. ["gap-1" "5"], and then walking over it, and for every non-
match, check if it becomes a match if concatenated with the string before it. Loop until the vector stops changing.

But my basic advice is: make sure the basic, free-standing form works (:gap-1.5), and that will be enough. Sugar is just sugar, after all, and best-effort level of support is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants