axelf4 / dumle

🍬 Rust HTML templating and virtual DOM library
27 stars 1 forks source link

A simple framework that I think it is similar to Imba! #1

Open limira opened 5 years ago

limira commented 5 years ago

Hi, I create an issue here because I don't have a reddit account and because you say that Doduo inspires by Imba. I implement a simple framework called Simi (inspired by Yew), and later I find out that it's similar to Imba (read my comment here). Some similarities I found between your ideas for Doduo and Simi:

But the way Simi generate code is different to your envision. If you want to see the code generated by Simi, you may try its simplest examples/counter and uncomment the //@debug (line 31) to see it.

I may read your ideas more careful later and come back to correct any misunderstand (my empty stomach make my eyes weary now :weary: )

axelf4 commented 5 years ago

Simi looks really great! I'll archive this in favor of Simi. I haven't had enough time to look through all the code, so take this with a grain of salt, but the main differences I see that you could consider are:

limira commented 5 years ago

I'll archive this in favor of Simi

It's very great if we can collaborate! If you want, I suggest you may want to play with Simi a little bit to see how you feel about it before any decision. Because Simi is already there (although still a WIP) and have some similarities with your ideas. Feel free to ask any question if I did not document (good) enough.

Usage of a keyed list diff algo. I think the LIS crate I wrote would be great for Simi

Simi does not support keyed list yet. But I am sure I will have a look at LIS when implementing it.

Bump allocation instead of on heap. Should be a small perf improvement.

After the first render, Simi try to directly update the current-only-virtual-DOM rather than rendering a new virtual DOM. Therefore, I think pump allocation is not suitable for Simi. But maybe it is suitable for keyed-list?

axelf4 commented 5 years ago

I have done some experimentation in the zerosize branch, and came up with something that takes a quite different approach from Simi. Basically using zero-sized types and macro_rules instead of proc-macros. It is a little messy, and I'll see about cleaning up the code, but what do you think?

limira commented 5 years ago

It's very interesting to see the very first steps of a new project (especially, building by a more experienced dev). Your work is far more Rusty than mine.

No thing from Imba, no bump allocation for now? Any plan to introduce/implement them? But from my understanding, currently you go with traditional virtual DOM (rerender the vDOM on every update then diff and patch), it make "The main distinguishing ideas are that we reconcile while building the virtual DOM" (quoted from README) impossible!?

I think you want to allow multiple UseStates (similar to component?) in an app? They are isolated to each other (no communication between them)?

I used to sprinkle arbitrary code inside a renderer. It's not a problem for a small app. But in a big app, I tend to lost in my own code. Therefore, Simi don't allow Rust code appear in its renderer (a renderer should focus on rendering, hence I never have to dive in the renderer to find codes that do non-render stuff). How do you think about this? For example, how to move this and/or this out of html!

axelf4 commented 5 years ago

Thanks for taking a look!

currently you go with traditional virtual DOM (rerender the vDOM on every update then diff and patch), it make "The main distinguishing ideas are that we reconcile while building the virtual DOM" (quoted from README) impossible!?

Yes, it works differently from the draft in the README. But it is still incremental DOM like Simi/Imba. The difference from traditional virtual DOM (thinking seed, doduo, etc) is that building the VDOM is zero cost. That is because the html! macro turns, for example:

html! {
    <div>
        <button />
    </div>
    <img />
}

into (Child(div, button), img) which is a zero-sized type. Thus the compiler is able to, in theory, inline the whole reconciliation phase into something that could have been generated by the component! macro from Simi. Bump-allocation I still think is relevant both for this and for Simi, however as you said it gets harder when you only re-render the current component/UseState. I think dodrio solves that by having an arena of bump allocators available, which is something to look into in the future.

I think you want to allow multiple UseStates (similar to component?) in an app? They are isolated to each other (no communication between them)?

You could have multiple UseState but, while they are based on React's useState hooks, in Rust they become a lot less ergonomic. Definitely room for improvement.

Simi don't allow Rust code appear in its renderer (a renderer should focus on rendering, hence I never have to dive in the renderer to find codes that do non-render stuff). How do you think about this?

I am fine with it as long as it doesn't mutate any state. That said, for stuff like callbacks I think messages are the way to go, like how they are implemented in Simi. It's just that I think that it's out of scope for this small virtual DOM library.

limira commented 5 years ago

The difference from traditional virtual DOM (thinking seed, doduo, etc)

Do you mean "(thinking seed, dodrio, etc)" :D?

Thus the compiler is able to, in theory, inline the whole reconciliation phase into something that could have been generated by the component! macro from Simi.

Wow, that's like magic! But I do not have enough knowledge (especially in Rust) to figure out! I can't wait to see a benchmark (and hope it goes well)! :D

they are based on React's useState hooks

It was not there when I learnt about React. I moved away from React for awhile. It's definitely on my to-read list (to try to understand what you want to do with UseState).

for stuff like callbacks I think messages are the way to go, like how they are implemented in Simi

Yeah, in the case that this happens, your crate may be a greate alternative, I may consider switching to it from Simi.


How about checking mismatched tags at compile time like this (you may want to paste the code into Rust playground to see how it works):

macro_rules! match_tag {
    // Unfortunately, this approach requires you to define a rule for each tag
    // So, for html tags, this requires more than 100 rules. How many are svg tags?
    (@match div div) => {};
    (@match span span) => {};

    (@match $open:ident $close:ident) => {
        compile_error!("Mismatched tags. See next error to find out where the mismatch occurs");
        // The next line forces error at the mitmatched close tag
        match_tag!($close);
    }
}

macro_rules! html {
    (<$open_tag:ident> $($tt:tt)*) => {
        html! { @tag $open_tag $($tt)* };
    };
    (@tag $open_tag:ident </$close_tag:ident>) => {
        match_tag!(@match $open_tag $close_tag);
    };
}

fn main() {
    html! { <div></span> };
}
axelf4 commented 5 years ago

Do you mean "(thinking seed, dodrio, etc)" :D?

Nice catch! Should probably come up with a new name since doduo no longer fits.

I can't wait to see a benchmark (and hope it goes well)!

That would probably be a next step! I reckon it's not very fast at the moment, since it always has to walk the DOM tree, as DOM nodes aren't stored, but the Web IDL Bindings Proposal should reduce the overhead for free. Upcoming Rust features, such as specialization; const generics; and custom allocators, should also help.

Yeah, in the case that this happens, your crate may be a greate alternative, I may consider switching to it from Simi.

Don't say that yet - we haven't seen any proofs of performance. ;)

How about checking mismatched tags at compile time like this

That's a good idea. I looked at const functions to do that in a less hacky manner, but that functionality was not ready yet - unlike your solution.