maciejhirsz / kobold

Easy declarative web interfaces.
https://docs.rs/kobold/
Mozilla Public License 2.0
385 stars 7 forks source link

Question: Kobols vs Dioxus #48

Closed 9876691 closed 1 year ago

9876691 commented 1 year ago

So I've tried different HTML generators with rust, i.e. markup.rs and ructe for example.

I finally ended up using Dioxus SSR because it supports a component model.

You can see a component library I've built up here https://github.com/purton-tech/cloak/tree/main/crates/primer-rsx

The main things I need are

  1. Default properties. i.e. for an input I want to be able to default most attributes and just supply the ones that I need. i.e. type: Option
  2. Loops - Some way of lopping and using a variable from the loop. i.e. creating tables. See an example table
  3. Child components. i.e. a Card has a header and a body.
  4. Fast compile times. when doing web I want sub 1 second incremental compiles times ideally.
  5. The ability to do raw html. i
  6. Nice to have - Type safety, i.e. don't let me do h7 but h1-h6 are ok.
  7. Allow me to add web-components in the markup.
  8. A pre-built component library so I don't have to build my own.
  9. Server side rendering.

How does that sound?

maciejhirsz commented 1 year ago
  1. Is partially done, planned for 0.7.
  2. Kobold proc macros are relatively light (no syn, not even proc_macro2 in release builds), though there is no hot reloading of components like in Dioxus.
  3. Not implemented, but I've toyed with it and should be a fairly straightforward feature. 0.7 maybe.
  4. That wouldn't be exactly type safety in Kobold since tags don't exist as types (are compiled to JS), but can definitely add a hashmap of valid tags and attributes to the macro and error on them. 0.7 maybe.
  5. Not on the roadmap, should be fairly easy though.
  6. Not on the roadmap (though I'll probably do a few, like the QR code).
  7. Not on the roadmap. This would be a major undertaking (like v2.0), I have no need for SSR myself currently.
nanoqsh commented 1 year ago

Hi! Awesome library. I don't want to create a new issue, I just want to ask. I think, this is partially related: do you plan to make the macro syntax more convenient?

For example, instead of yew-like syntax:

<h1 class="title">{ "Hello world!" }</h1>

In Dioxus you write:

h1 { class: "title", "Hello world!" }

This is a matter of taste, but it seems that such a syntax is more convenient and simpler. Personally, I don't see the point in repeating closing HTML tags. I also tried to write in Seed framework, where it looks like this:

div![
    C!["title"], // shortcut for class attribute
    "Hello world!",
]

There is also no need to repeat the closing tags. Although this approach has the disadvantage that you can't just copy a piece of HTML into the rust code, however, writing a view directly is much easier. It also promotes readability, unlike classic HTML.

maciejhirsz commented 1 year ago

@nanoqsh I wanted to write a short reply, and this came out, I apologize in advance as it turns out I have opinions about this :sweat_smile:.

This is obviously super subjective, but I find that style less readable, and in my experience readability always trumps writability as typing is probably the least time consuming part of me actually programming.

There is a couple factors that contribute to this:

  1. The code is less distinct from regular Rust. That might seem like an advantage, but scanning through code the patterns of angle brackets jump out immediately: this is declarative view code that's different from its surrounding. The macro (whether in Kobold or Dioxus) is effectively invoking an embedded DSL, and I think having this strong visual distinction helps a lot to mentally switch between domains.
  2. The view code becomes very concise. Being concise is of itself not a bad thing, after all I do have CSS syntax for classes for that reason. That said I think we can all agree that if being concise was always good, then regex would have been the pinnacle of syntax. Jokes aside, consider this:
    render! {
        div {
            class: "col-sm-6 smallpad",
            button {
                class:"btn btn-primary btn-block",
                r#type: "button",
                id: *id,
                onclick: move |_| onclick.call(()),
                *name
            }
        }
    }

    Can you immediately tell whether the button here has a body? What if it didn't need a deref, could that be a shorthand for name: name instead?

  3. Realistically what we need are two separate blocks: one for attributes, and one for children. If I were to re-apply that button structure in a hypothetical Kobold syntax using blocks it could look something like:
    view! {
        // Keep the CSS selectors for classes, but use strings (bare identifiers for variables)
        div."col-sm-6"."smallpad" {
            // `id` and `onclick` are shorthands
            button."btn"."btn-primary"."btn-block" [type="button", id, onclick] {
                name
            }
        }
    }

    or, without CSS selector syntax:

    view! {
        div [class="col-sm-6 smallpad"] {
            // `id` and `onclick` are shorthands
            button [class="btn btn-primary btn-block", type="button", id, onclick] {
                name
            }
        }
    }

    or, expanded:

    view! {
        div [class="col-sm-6 smallpad"] {
            // `id` and `onclick` are shorthands
            button [
                class="btn btn-primary btn-block",
                type="button",
                id,
                onclick,
            ] {
                name
            }
        }
    }

    I don't hate this, but it doesn't spark joy in me either.

Simpler? Absolutely yes! It's actually harder to implement the macro the way it is since angle brackets are the only kinds of brackets that aren't treated as group delimiters in Rust token structure. Convenient? To write - sure. To read - debatable.

If your main objection is having to repeat closing tags it would be quite easy to get rid of them using </> to infer the closing tag:

view! {
    <div.col-sm-6.smallpad>
        <button.btn.btn-primary.btn-block type="button" {id} {onclick}>
            {name}
        </>
    </>
}

This way you could still put the full </div> in if it helps readability (if opening and closing are more than half a screen apart from each other would be my heuristic), but there really isn't anything that would prevent this - there is no ambiguity about which tag name should follow </.

Edit: To that last point I went ahead and implemented it, but I'm not sure there is really all that much value to be gained here, here is the main component of the TodoMVC example with implicit closing tags:

https://github.com/maciejhirsz/kobold/blob/c55612622ef0c1bc2c470cb3ca11688cb4a5e718/examples/todomvc/src/main.rs#L22-L65

Some of it seems kind of worse, like the double </></> at the end of the line for </a></p>.

Edit 2: Just noticing this, but with CSS selectors for that h1 example, Kobold is actually the shortest already :upside_down_face::

// Yew
<h1 class="title">{ "Hello world!" }</h1>
// Dioxus
h1 { class: "title", "Hello world!" }
// Kobold
<h1.title>"Hello world!"</h1>
nanoqsh commented 1 year ago

@maciejhirsz

Thanks for the detailed reply. I understand and fully agree that readability is more important than typing. Yes, my only complaint about HTML is the need for a meaningless tag repetition. So I find the <span>"text"</> syntax much more satisfying.

9876691 commented 1 year ago
// Yew
<h1 class="title">{ "Hello world!" }</h1>
// Dioxus
h1 { class: "title", "Hello world!" }
// Kobold
<h1.title>"Hello world!"</h1>

I can live with all of those syntaxes. And I think if you shop around other libraries have implemented them all in one form or another.

My problem is, I've had a large code base using markup.rs and the compile times seemed to get slower and slower. Things seem ok with Dioxus.

A 14 second incremental build time is awful when trying to build UI.

Just my thoughts.

maciejhirsz commented 1 year ago

My problem is, I've had a large code base using markup.rs and the compile times seemed to get slower and slower. Things seem ok with Dioxus.

Compile times in Kobold should be fine, kobold_macros crate doesn't pull syn or quote (syn is the biggest offender when it comes to compile times since it can include AST parsing for the entire Rust syntax), and only uses proc_macros2 for tests. Just looking at it markup is using all 3, so yeah. It's not the highest priority for me, but it is always on my mind, when working on macros. I even do things like use a thread_local ArrayString buffer to temporarily stringify identifiers so I can inspect them without allocating strings.

My dev machine is a desktop with 5950X, so granted not your baseline laptop, but when working on TodoMVC example I get sub-1s incremental compile times.

maciejhirsz commented 1 year ago

I believe this is answered, so closing it. Feel free to reopen it if you have other questions.

maciejhirsz commented 1 year ago

@ianpurton just FYI, the PR for default parameters just landed: #67