Create: 2023-07-18, Update: 2023-07-18
Sometimes, too many choices drive me insane.
A while ago, I rewrote my blog backend from Go to Rust. It gave me some unknown confidence that doing full-stack web development in Rust would be easy.
Later, from my favourite YouTube channel ThePrimeagen, I know about Leptos. I thought it would be a fun experience because I have never done full-stack web development in a single language. I used to write a Go backend and use Svelte as the frontend, that approache is still the best in my opinion.
However, I already know how to do that. And that was no challenging enough to make me feel excited. So I look for something new, and more importantly, more performent. Rust comes into my sight.
Now, I want something more interactive. At least with a login system, but how?
Leptos is a full-stack Rust web framework using signal, just as Svelte does. And it makes use of wasm, which I think is the greatest thing in a while in the web scene. Thanks to wasm, I was able to port a simple game written in C with SDL to be fully playable in the browser. See this. You may even try the game here. (The game was abandoned shortly after.)
Leptos is the future, I thought. But just as many other "frameworks", writing leptos is not like writing plain Rust. I have to learn the tool first.
However, I abandoned Leptos. Not because it is hard to learn, but because the syntax is so damn ugly and those leptos code cannot be some experience that you can easily carry over to another Rust project which does not use leptos.
Here is a few things that I dislike about leptos.
move |_| lambda function. I have yet to see anywhere in Rust make use of this many move keyword.
jsx like HTML macros. I hate having html tags in my Rust code, because it is inside a macros, the indentation is horrible. (This may be due to my Emacs config, the lsp does not indent the html inside the macros.) Also, such macros is hard to debug.
routing. This is the last straw on the camel's back. I know how to do rounting in Axum. Why the fuck should I use some HTML-tag-like syntax to write my routing in leptos? Moreover, such routing is not stand alone usable, they need to be in a Axum router with a weird function as a service.
leptos ruins axum extractor. The best part of Axum is its great extractor, I can extract form, json, cookies with a simple function parameter. When I use leptos, boom, that is fucking gone. Instead, if I insist on extracting something, I have to do my own extraction, which is so stupid, given that the same work can already be done in Axum.
ssr feature confuse the lsp. Leptos offers a very interesting thing that you can call any Rust function such as doing database stuffs directly in Leptos handler. But that comes at a cost, the handler now must be ssr, which make sense. It simplifies a lot of the repetitive work of writing api and consuming api. However, some code must be inside the ssr feature tag, and the lsp automatically disable linting and checking on dead code. It makes writing certain part of code a nightmare.
the generated api is hard to be consumed by other applications. Apart from doing web development, I would write some iOS apps, where I wish I can use the same api endpoints. Since leptos generated the api implicitly for us, those api are not controlled fully by myself. What if I want to return an error in a specific format? What if I want to return a json contains some extra information which is useful in the iOS app only? No, I can do nothing. If I want my backend to serve to different clients, I would still have to write those api by hand. Then, what is the point of the leptos implicit api at all?
I gave up using leptos. I may look into it later, it is still interesting, but currently not so useful to me. Svelte has a much better syntax, and I doubt the performance gain by using leptos is very little.
I have not yet give up on using Rust at the only resource to write a full-stack web application.
It leads to using the good old HTML templating method, simple, reliable, no magic in the background.
Ok, let's find a template library. Go has its go-template, which is a built-in library. Rust on the other hand, does not have a standard template, instead, it has tons of community libraries.
There are too many choices. It confused me. Which one should I use, should I choose based on popularity? Performance? Or simplicity?
In the blog backend, I opt to use tinytemplate. Simple, fast, no bullshit. I gave up using it because it requires me to make so many structs for the rendering engine to work.
Later, I found Askama. A compile time generated templating engine. Fast. The syntax is clean. However, if I make some change in the HTML template. I would have to recompile the whole application in order to see the change in the browser. In Go, it was not a problem. In Rust, god damn it, even I have not made any change in the .rs files, just modified something in the template. It takes a few seconds, at least two second to compile, sometimes up to five. I already make use of sccache to increase the compile speed.
I thought to myself, if the application needs to be recompile anyway, I may just write HTML directly in the .rs source files with a prettier format. And I switched to maud. This is a very good library. It simplify the way I write HTML tag and closing tag. Also, I no longer have to write any angle brackets. Great! It uses a macros to achieve such a thing. Fuck, macros again? The code suffers from indentation issues just like the leptos one. Also, when the HTML is nested in so many layer. I would either have to extract it into some one-off functions, or accept my fate to see the words marching towards the right margin.
I hate macros. What about a templating library which is macros free? I came across stpl, an unmaintained libary which use function only way to write HTML. The last time it got updated was in 2018. (WTF) Then I see this example in leptos. Interesting. But I just want some plain HTML without any leptos reactivity. I like this syntax. So I created my own templating library, shapeless-html.
I was satisfied for some time. Until I decided I would like to use tailwindcss in my project. Oh god. How many .class functions do I need to write? I decided to write the html and use firefox to access the local file and css to prototype the layout. Otherwise, every little change leads to a few seconds of compile time. Then, I would rewrite the html in my own templating engine.
What a stupid idea! I wasted so many man hour to rewrite little things in my own so-called library. I decided I can use a cli program to automate the process. Then I wrote this. It basically read an html file and translate it into my own templating code.
That was another stupid idea. I gave up. I tried to go back to tinytemplate and load the template file every time I got a request. It works. I no longer have to recompile when I make changes to the template HTML files. But the boilerplate inside my code is such a mess.
Last, I found tera. Another templating engine, which is as popular as Askama. I settled in tera. Not because it has anything great syntax wise or in performance. Just because it has a built-in full function which reloads all the templating files. So that I can reload the templates in debug mode every time a request is called.
Most importantly, I have been delaying my completion of a simple project just for the above explorations. So I currently settled in tera. But tera is so feature rich, that I rarely use most of its features. I am afraid that I would write my own templating library again by stripping out the unnecessary features.
There are just way too many choices in just templating libary in Rust.
The last project was a diary site for me and my girlfriend, where we write a diary daily and each other can view the other's diary after two weeks. That was a fun idea.
This site does not require any reactivity. But what if I want some? Do I go back and use leptos? No way.
Fortunately, I discovered htmx. This is such a refreshing way to have interactions in web pages.
Later, I found out alpine.js and hyperscript. I think I would no longer have to write any JavaScript in the future.
My next blog post should be something about them.