render-rs/render.rs

A less dom and more template approach

daaku opened this issue ยท 4 comments

daaku commented

Currently the way the rsx! macro parses and outputs something akin to a psuedo-dom style set of element and attributes is similar to what React does. But the current use case of this crate is server side rendering (unless you have plans for this with wasm), a dom like structure is an implementation detail. With this in mind, it would be more performant to pass thru most of the template as static bytes to write calls, and splice in dynamic content where we find it.

As an example, for this component:

#[component]
fn Container<'kind, Children: Render>(
    kind: &'kind str,
    children: Children,
) {
    rsx! {
        <div class={kind}>
          {children}
        </div>
    }
}

Old output:

let result = {
    let Container { kind, children } = self;
    {
        ::render::SimpleElement {
            tag_name: "div",
            attributes: {
                let mut hm = std::collections::HashMap::<&str, &str>::new();
                hm.insert("class", kind);
                Some(hm)
            },
            contents: Some(children),
        }
    }
};
::render::Render::render_into(result, w)

New output:

let Container { kind, children } = self;
write!(w, b"<div class=")?;
::render::Render::render_into(kind, w)?;
write!(w, b">")?;
::render::Render::render_into(children, w)?;
write!(w, b"</div>")?;
Ok(())

I think there are other things to consider with this change, like automatic quoting of attribute, punning (which I independently am unsure of) etc. I'll figure that out as I make progress. I'm going to experiment with this idea and see how far I can take it without running into unforeseen issues.

Thoughts?

it would be more performant to pass thru most of the template as static bytes to write calls

That's probably true but I wonder if that can be optimized by the compiler? I'm not sure how strong it can get ๐Ÿ˜ƒ because SimpleElement output should be kinda similar to the "new output". I made the SimpleElement struct because it was easier for me, but if we can see a real performance gain we can maybe just write the bytes directly!

Regarding DOM-specific things, I agree that it might be weird to have this ideas baked in, but do you agree that it would be mostly used as an HTML template generator having this special case is better?

  • If you want to have a "custom component" that renders whatever you want, you can use a struct/"function component". I imagine it like React Native, where you don't use lower case components at all. Everything is a "custom component", whether provided by the user or by the framework itself
  • You can always return a string because it is Render
  • You can always just write! if you want to (like we do internally ๐Ÿ˜„)
  • As you mentioned the quoting, we also escape the strings, which I'm not sure we should always do, but probably need to do that automatically for HTML elements' attributes and (string) children so XSS won't be an issue if you use Render with a web framework

Also, lately I found maud which returns an owned string. Owned strings should be less efficient, memory wise, than what we do. But they say that practically it is faster. I don't think we should micro-optimize things that will hurt expressiveness and ease of use. What do you think about that?

(It's sometimes a problem to express feeling in writing, especially when having a language-barrier. Just to be on the safe side, These are genuine questions, would be happy to get your feedback ๐Ÿ˜ƒ)

daaku commented

That's probably true but I wonder if that can be optimized by the compiler? I'm not sure how strong it can get smiley because SimpleElement output should be kinda similar to the "new output". I made the SimpleElement struct because it was easier for me, but if we can see a real performance gain we can maybe just write the bytes directly!

I'm pretty sure the compiler can't optimize away the use of the HashMap / HashSet -- but I'm no expert, will need to investigate.

Regarding DOM-specific things, I agree that it might be weird to have this ideas baked in, but do you agree that it would be mostly used as an HTML template generator having this special case is better?

To be clear, I'm not suggesting supporting non HTML things -- I'm just talking about the internal implementation to prefer passing bytes thru rather than parsing individual tags into nodes etc.

If you want to have a "custom component" that renders whatever you want, you can use a struct/"function component". I imagine it like React Native, where you don't use lower case components at all. Everything is a "custom component", whether provided by the user or by the framework itself

With what I'm suggesting, the way to write components will not change. In fact, I'm pretty sure most current code will just keep working (only punning is what I'm unsure of).

Also, lately I found maud which returns an owned string. Owned strings should be less efficient, memory wise, than what we do. But they say that practically it is faster.

This is interesting. I'm not sure what they tested exactly, but would be interesting to find some benchmarks and get clarity on the testing.

I don't think we should micro-optimize things that will hurt expressiveness and ease of use. What do you think about that?

Again I want to be clear that the ergonomics of using the library will not be affected by what I'm proposing!

(It's sometimes a problem to express feeling in writing, especially when having a language-barrier. Just to be on the safe side, These are genuine questions, would be happy to get your feedback smiley)

Absolutely! It's good to discuss these things and thanks for letting me know English isn't your primary language :) Feel free to be frank and direct!

I'm going to experiment with it, because I'm still convinced it will be easier/faster/better in various ways. I'll also write some benchmarks so we can get some real numbers to compare as well. If it doesn't work out that way, I'll be fine to discard the work :)

To be clear, I'm not suggesting supporting non HTML things -- I'm just talking about the internal implementation to prefer passing bytes thru rather than parsing individual tags into nodes etc.

Ah, that sums it up ๐Ÿ˜„ basically inlining SimpleElement, no? I think it can be good ๐Ÿ˜„

But if that's too difficult and harder to test, maybe we can drop the HashMap/HashSet usages in favor of arrays of tuples (like I thought in #6)? Not sure if this can be optimized away either but it feels lighter ๐Ÿ˜„

I'll be happy to see the results ๐Ÿ‘

daaku commented

Ah, that sums it up smile basically inlining SimpleElement, no? I think it can be good ๐Ÿ˜„

Yes, basically!

But if that's too difficult and harder to test, maybe we can drop the HashMap/HashSet usages in favor of arrays of tuples (like I thought in #6)? Not sure if this can be optimized away either but it feels lighter ๐Ÿ˜„

That might actually be something the compiler can optimize away!