teenjuna / leptos-altview

Alternative implementation of view macro for Leptos.
MIT License
1 stars 0 forks source link

Getting started #1

Open teenjuna opened 1 year ago

teenjuna commented 1 year ago

So, in this Discord thread we came up with an idea of how an alternative, rustfmt-friendly macro for Leptos could look like.

Here is a snippet: playground.

There still needs to be a Discussion on how to deal with props. Personally, I don't like the builder pattern achieved with the #[component] macro. Writing snake_case-named components with normal Rust arguments looks cleaner. But we need to support normal Leptos components.

Right now it's worth making a SPEC.md file that describes the main parsing rules of the macro.

teenjuna commented 1 year ago

RE props. Maybe we can use different syntax for different components.

Components that use builder (normal Leptos components):

For(
    each = move || counters.get(),
    key = |counter| counter.id,
    view = move |counter| view![counter.count.get()]
)

Components that use normal Rust arguments:

For(
    move || counters.get(),
    |counter| counter.id,
    move |counter| view![counter.count.get()]
)

Components that use struct for props:

For(ForProps {
    each: move || counters.get(),
    key: |counter| counter.id,
    view: move |counter| view![counter.count.get()]
})

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=58a30caf525f07bcae6779e1b7ea1657

So, you could still create components with the #[component] macro, but alternative view! (they should be usable from native view! macro). Or you can create components with normal arguments or props structs (such components may not be usable with the native view! macro).

teenjuna commented 1 year ago

RE snake_case-named components. Native macro doesn't support them, as they can't be differentiated from the normal HTML elements. I wonder how Dioxus does this. I have a feeling that we can add use leptos::html::* to the beginning of the expanded macro code. Then we would rely on the normal Rust scoping. If you create a component named header and try to use it inside other component, you wouldn't able to, because leptos::html::header would overwrite it. But you would be able to use self::header. That's the behaviour of Dioxus, so they may also use this approach.

Also, if we go with that approach, then this example from original snippet won't work:

"custom-web-component-with-dashes"(enabled = true),
custom_web_component_with_underscore(enabled = true),

Because custom_web_component_with_underscore won't be found in the scope. Instead, we would do this:

"custom-web-component-with-dashes"(enabled = true),
"custom_web_component_with_underscore"(enabled = true),

Which makes sense, since it clearly indicates that these elements are coming from somewhere else.

teenjuna commented 1 year ago

For macro implementation, we can take a look on how it's done for https://docs.rs/mogwai-dom/latest/mogwai_dom/macro.rsx.html

teenjuna commented 1 year ago

@Jinxit what do you think?

tqwewe commented 1 year ago

Just looked at the playground link, I love how it works with Rustfmt as is! The biggest issue I have with it though is the separation between props/attrs and children are not so clear for me. It seems like children are the last items, and do not have any =, but it still might be nice to explore a way to clarify the distinction to better visualize the layout of the elements.

For example, this seems to work:

view![
    div(
        class = "class1",
        class = (
            foo = move |_| signal.get(),
            "bar baz" = move |_| signal.get()
        ),
        on = (click = move |_| {}, keydown = move |_| {}),
        custom_prop = "custom_prop_value",
    )([
        div(
            class = "class2",
            div(class = "class3"),
            div(class = "class3"),
        ),
        div(
            class = "class2",
            div(class = "class3"),
            div(class = "class3"),
        ),
    ]),
];

Not sure if I love it though :')

teenjuna commented 1 year ago

@tqwewe I thought about something like this:

fn main() {
    view![
        div(
            id = "id1",
            class = "class1",
            on = (click = move |_| {}, keydown = move |_| {})
        )(
            div(id = "id2", class = "class2"),
            div(id = "id3", class = "class3")
        ),
        div(),
    ];
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f8e587cc65797376c02a31cbba588edd

It looks nice when it's folded, but inline is kind of meh

teenjuna commented 1 year ago

This is a nice snippet for comparison:

fn main() {
    view![
        div(
            id = "id1",
            class = "class1",
            on = (click = move |_| {}, keydown = move |_| {})
        )(
            div(id = "id2", class = "class2")(div()),
            div(id = "id3", class = "class3")
        ),
        div(),
    ];

    view![
        div(
            id = "id1",
            class = "class1",
            on = (click = move |_| {}, keydown = move |_| {}),
            div(id = "id2", class = "class2", div()),
            div(id = "id3", class = "class3")
        ),
        div(),
    ];
}

The problem with the explicit props/children is that if you have a div that only has children, it would look like this: div()(...), which is meh. Second variant would be div(...). Just empty div could be div in both cases.

tqwewe commented 1 year ago

And for the sake of exploring all options, another option might be:

view![
    div(
        id = "id1",
        class = "class1",
        on = (click = move |_| {}, keydown = move |_| {}),
        [
            div(id = "id2", class = "class2"),
            div(id = "id3", class = "class3")
        ]
    ),
    div(),
];

The problem with the explicit props/children is that if you have a div that only has children, it would look like this: div()(...), which is meh.

This snippet doesn't have that issue at least!

teenjuna commented 1 year ago

But this adds extra tab, which sucks :( I wish that worked:

fn main() {
    view![
        div(
            id = "id1",
            class = "class1",
            on = (click = move |_| {}, keydown = move |_| {})
        )[
            div(id = "id2", class = "class2")[div()],
            div(id = "id3", class = "class3")
        ],
        div(),
    ];
}

But it breaks rustfmt :(

teenjuna commented 1 year ago

So, we continued the Discussion in the discord thread and chose this form:

fn main() {
    view![
        div(
            id = "id1",
            class = "class1",
            on[click] = move |_| {},
            on[keydown] = move |_| {},
            div(id = "id2", class = "class2", "Just a string"),
            div(id = "id3", class = "class3"),
            div("Another string"),
        ),
        div(
            id = "id1",
            class = "class1",
            on[click] = move |_| {},
            on[keydown] = move |_| {},
            //
            div(id = "id2", class = "class2", "Just a string"),
            div(id = "id3", class = "class3"),
            div("Another string"),
        ),
        div(
            id = "id1",
            class = "class1",
            on[click] = move |_| {},
            on[keydown] = move |_| {},
            [
                div(id = "id2", class = "class2", "Just a string"),
                div(id = "id3", class = "class3"),
                div("Another string"),
            ]
        ),
    ];
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d648dbe67cd5026fda674d3dabd62d24

@tqwewe already started implementation and I'll try to add a spec later today.