(I am the maintainer of htpy). We use htpy for a big project with a lot of complex components/pages. We have yet to see htpy being a problem for performance reasons. For us, the performance problems always seem to be in our database queries or how we process our data. Also, to be clear, a lot of big sites use the Django template system which htpy seems to typically beat on performance. There are faster ways to generate HTML but htpy is not really slow either!
So to us, htpy performance has never been an issue which is why we have not been optimizing it further. If anyone is interested in re-implementing parts of htpy in cython/rust/something or improve the performance in other ways, that should be quite doable. The core htpy element/attribute implementation is a few hundred lines of code. It is heavily tested with 1000+ lines of tests. It could be a quite fun project, contributions are welcome!
In our company, where htpy was born, we are building a highly interactive application with htpy combined with Alpine.js+htmx. We have a couple of thousands lines of htpy code in production right now. We stick all HTML generation code into components/x.py or components.py files to keep it separate from other code. It is easy to grasp the structure. We use type hints so it is clear what data different components expect. "Goto defintion" just works so it is easy to navigate the code.
I agree about that HTML looks better with tags and it takes a bit of getting used to the python syntax. If something like JSX was possible in Python with all the tooling working, that would be great.
For what it's worth. One thing I really like about `htpy` is that the element attributes go before the child elements. I find this easier to write and read. Other things I like:
Having child elements as a list (i.e: the __getitem__ override) makes it convenient to build elements based on simple conditions + list comprehensions. This can be done with other frameworks, but it seems more natural to me when using `htpy`.
I also like that you can just `print()` elements and get the final HTML without having to pass it through a different function. This is not something specific about FastHTML, but rather something I've found I also had to do when using `lxml` or similar tools (I wrote about my experiments here[0])
I wrote a few things with each of FT and htpy, and looked at the resulting code -- I felt like the htpy approach was slightly less neat personally. htpy has the benefit that '.' and '#' can have special meanings, but the downside of needing to use both __getitem__ and __call__. I didn't feel like that was a tradeoff I wanted to make. I actually originally wrote FT for a different purpose (XML for language model input) so id and class attributes weren't of any interest at all at that time!
Also, I was able to implement FT using just 2 lines of code -- it felt like a very natural data structure that was a good fit with Python.
Having said all that, I think htpy is really nifty and elegant. :D
FastHTML is very interesting and reading this thread has led me to discover htpy as well which I am shocked I have never seen before! The htpy website and docs are also great. So now I am a bit of a dilemma over which one to use.
I actually hate working in HTML with all those closing tags etc so I nearly always set up a build/make process to edit my templates in PUG format. When I paste my PUG->html output into https://h2x.answer.ai/, or run html2htpy over them, I get python code that basically looks the same as those PUG templates. What a realization that is! So I may as well create and edit them in python rather than PUG and exploit the power of my beloved python dev environment and tools (as nicely stated in that "Throw out your templates" essay https://github.com/tavisrudd/throw_out_your_templates reference from the htpy docs). Thanks very much Jeremy and Andreas for this fantastic insight :)
Writing out the code to generate this may not be easier than using templates, but it is not really harder either. I think it mostly looks a little different.
The real benefit is using this bootstrap modal component within your app, since it is just a regular function call. You can leverage your editor "goto definition" and static type checking within all the functions make up your web sites/apps HTML.
Whoa, a creative (in a good way IMO) use of __ getitem__() dunder method to depict attributes and children. No action at a distance using with blocks, static and strongly typing instead of stringly typing, no need to extend Python, composable from fragments.
Have you tried to "bring up front" if and for?
For example
> if_(cond, tag1(), tag2()) # without eagerly execute both branches
Instead of
> tag1() if cond else tag2()
And
for_(cars, lambda car: car_details(car)) # while still being type checked
Python's inline if's still read backwards to me and it would be nice to switch the order. But we are kind of stuck with it. I also don't want to introduce new control structures in this lib. Just want to use whatever Python has to offer in terms of control structures/syntax to make it more approachable.
htpy supports generators/callables as children (https://htpy.dev/streaming/). As long as you are careful to wrap things in generators/lambdas everything is lazy. Implementing if_ and for_ is straightforward and anyone can build constructs like that to use. But will not be lazy unless all arguments are wrapped in lambdas:
from htpy import div, li, ul
def if_(cond, a, b):
return a() if cond() else b()
def for_(items, func):
return (func(item) for item in items())
print(div[if_(lambda: True, lambda: "True!", lambda: "False!")])
print(div[if_(lambda: False, lambda: "True!", lambda: "False!")])
print(ul[for_(lambda: ["a", "b", "c"], lambda x: li[x])])
https://htpy.dev/usage/#passing-data-with-context