RSS Amplifier

Edger Dev · Mar 30, 2024

CSS Build Revisited for dioxus-class

0
Sign in to vote or save

YJ Park · Edger Dev

I've created a few crates last year, defines constants and modifiers for both tailwindcss and daisyui, to create a compiler checked experience in dioxus, which works fine, with a big caveat with the awkward building process. For more detail, check the previous post.
I planned to write some web apps with dioxus back then, though got distracted by other stuffs, so didn't put more efforts in improving it.

Lately I've got some new ideas for web apps, so plan to catch up with dioxus and also dioxus-class, I did update with dioxus 0.5, which is really great, all the scopes and lifetimes are removed, writing view logic is in a much, much nicer way now.

It's quite easy to update the demo app, but the server side render part was originally in a hacky way, so need more effort for an update, also I don't want to settle with this half way solution this time, so put some time on searching for better solutions.

This blog is about the new way, if you are not interested in the process of problem-solving, a short version of the solution can be found at BUILD.md

Tailwindcss needs a build process, you write the views with CSS values defined by tailwindcss, then this build process will identify these usages, and then generate actual CSS file for the views to use. You can use a CDN version for development, but it's big and not recommended for production usage.

In dioxus's tailwindcss example, the code is something like:

rsx!{
	nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center",
		a { class: "mr-5 hover:text-white", "First Link" }
		a { class: "mr-5 hover:text-white", "Second Link" }
		a { class: "mr-5 hover:text-white", "Third Link" }
		a { class: "mr-5 hover:text-white", "Fourth Link" }
	}
}
Copy

It's very similar to the original way used in HTML, JavaScript, TypeScript, so the build process can be tweaked easily with configuration changes.

But since I don't like these magic strings in codes, after using dioxus-tailwindcss, the same snippet will be like this:

rsx!{
	nav { class: class!(md(ml_auto) flex flex_wrap items_center text_base justify_center),
		a { class: class!(mr_5 hover(text_white), "First Link" }
		a { class: class!(mr_5 hover(text_white), "Second Link" }
		a { class: class!(mr_5 hover(text_white), "Third Link" }
		a { class: class!(mr_5 hover(text_white), "Fourth Link" }
	}
}
Copy

Now all the class definitions are done in rust code, so compiler will make sure there is no typo in them, which is nice. But now tailwindcss can't build these content properly.

For the simple changes like mr-5 to mr_5, some regex tricks might work, but not the modifiers or other changes for complex cases, such as bottom-1/2 to bottom_1__2, it's very hard to get it correct in text level, also if it's done in text level, then there is no guarantee that all valid rust codes will be covered.

Also, it'll be really trivial and error-prune in this approach, and I'm basically doing reverse engineer to my own code, which seems really stupid, there must be some better way.

My first idea is to inject some logic to the cargo clipper process, since which will just check all the source codes, I can just find all the class! occurrence, then print out the converted value.

After some research, I found dylint which seems fit my needs, since the cargo clipper is not open to extension, but with dylint, you can create lint, and it can load the custom lints as library.

Did set it up, and start reading the examples, which is really complex, I am reluctant to learn how to write lint for this particular purpose.

And I realized even if I get all the codes with class! calls, I still need a way to convert them to tailwindcss format, which means I need to actually run these codes, which seems not possible by using linting approach.

The problem with server side rendering is that I only want to run the codes related to CSS name conversion, not the whole page rendering logic, so I don't really need the server rendering, just some way to run these CSS conversion logics.

So I start looking for ways to get all the macro calls, and found proc, which just did what I want, so borrowed its implementation, change my previous macro_rule implementation for class! to proc_macro (did use syn and proc_macro2 in another project before, so this is quite simple to me).

So now by running cargo test --features "build-classes", I can gather all class! usage, save them into a file, then run build.rs with the content of the generated file, to finally generate the CSS values to an HTML file, then feed that file as input to tailwindcss.

Well, still quite a few steps, but they are quick steps, and can be automatically executed with cargo watch, at least, the correctness is guaranteed, and no extra logic needed in crates depend on dioxus-class, I'm really happy with current state.

Check BUILD.md for more details about these steps.

Since tailwindcss CLI itself is written in rust now, maybe it's possible to inject some logic there, but I still think anything working in text level is not as good as working in code level, so even that works with fewer steps, personally I will still prefer the current solution.

Another option is that I can create a DSL in rust with proc_macro, which is using exactly the same syntax with tailwindcss, so following codes will become valid rust codes.

class!(md:ml-auto flex flex-wrap items-center text-base justify-center),
Copy

It's pretty neat when I thought about it, almost want to give it a try, creating such DSL is not hard, just some tedious works, feels that I can get a working version in a few days.

Though after some more thoughts, I didn't take this approach.

Firstly, creating such DSL in proc_macro2 will make the code completion more complicated, since you have to run the proc_macro2 to check the tokens, it's not as easy as simple constants and modifiers, though in this case, the editor extension for tailwindcss can be tweaked easily, so you can get text based completion during editing, then compiler checked later when build the codes.

Secondly, these codes will be a bit hard to read, and maintain.

Most importantly, I'm not really willing to put this effort for a margin benefits to my self at this moment.

But maybe I can figure out some way to get this DSL without writing a lot of tedious codes, then this crate might be much more attractive to other developers, then probably I'll write another revisit on this topic. Until then, I'll try to write some apps with this instead.

No posts

Read the original on edger.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.