Seed: Use an existing virtual DOM implementation

Created on 18 Dec 2018  路  26Comments  路  Source: seed-rs/seed

Instead of reinventing the wheel, it might be helpful to use an existing crate like one of the following:

enhancement

Most helpful comment

there is also one nice, fitzgen/dodrio

All 26 comments

My two cents: dominator is stdweb based, therefore not a potential candidate, if using an existing crate will be considered seriously. But I just sneaked around in its sources and I found something which might inspire or help when the time comes, to add animations to seed. Dominator provides a requestAnimationFrame based runloop for animations. However not a physical engine for spring-like stuff. And because I couldn't get the examples running, and there is no documentation, I didn't dig any deeper (yet).

dominator is stdweb based

oh, I didn't see that. A

therefore not a potential candidate,

I totally agree!

Agree. From a skim, it looks like these are not especially modular, or at least not documented as such. Ie, could be done, but not without a lot of work. I like the idea of modular bindgen-based parts like this, but not sure how to approach integrating. Short of full-integration, implementing good ideas like requestAnimationFrame would be a start.

It would be nice to have a standalone, well-documented virtual DOM crate that any bindgen framework could use.

Hi, I am the author of Simi. Simi try to bypass the creation of new virtual DOM in the re-render process. Therefore, its virtual DOM is very special, I think it is not very useful for other frameworks.

You may interest in typed-html instead?

It looks like typed-html has a similar issue to what you describe with Simi: It appears tightly-coupled with its html macro.

Because Simi is going in a very different direction, I just have a quick look at type-html, hence may be my thought is wrong. But I think type-html's macro is just a helper to translate JSX-like syntax into its equivalent Rust code to build its DOMTree. If typed-html's DOMTree is suitable for your use case, you are absolutely able to create your own macro to implement a different syntax.

I think type-html is also not relevant here, because, as its name says, it is about typed HTML or plain HTML as syntax, like JSX. But seed uses a different approach to represent HTML, programmatically.

I am not sure it is relevant or not. If the answer is "no", then, the reason is not what you think: plain HTML as syntax, like JSX. Because you can ignore its default macro, and implement your own macro that supports a different syntax (similar to the way I try to implement an alternative macro - which is discontinued and never release - for Yew).

From what I understand, typed-html try to ensure correct use of html tags, attributes at compile time. You can look at
element.rs and see that it define traits such as: FlowContent, TableContent... That is the way to help validating correctness at compile time. The author say it explicitly in the README:

any element which accepts children will have a restriction on the type of the children

At the end of the README:

Render to a virtual DOM

... You can walk this virtual DOM tree and use it to build an actual DOM tree with stdweb or pass it on to your favourite virtual DOM system.

It's clear that one of the intention of the author is for use in a specific framework.

Problems that I can see:

  • typed-html is still only support stdweb
  • How to integrate with a specific framework such as seed or Yew? For example: integration with theirs message system?

instead of narrowing down on existing Rust virtual DOM implementations I just did the opposite: I read some articles on fast approaches for DOM updating, might be an inspiration when it comes to optimizing the Seed virtual DOM implementation.

This is a technique called DOM memoization:

I think similar concepts are also used by hyperHTML and litHTML, which position themselves as lightweight React alternatives.

I like the idea of moving to something more efficient than the current React-style vDOM (Eg avoiding recreating the whole seed::dom_types::El structure every state change). Need to evaluate existing approaches (Or come up with a new variant), and make major changes internally... but shouldn't affect API.

It seems like we'd still need the El struct to wrap web_sys Elements, but perhaps it would no longer hold things like text, attributes, children etc.

(Eg avoiding recreating the whole seed::dom_types::El structure every state change)

From my first comment in this discussion above:

Simi try to bypass the creation of new virtual DOM in the re-render process

In other words: Simi already took that path, and you (Seed) seem want to go that direction as well.
Interesting enough, Imba (from reading articles provide by @rsaccon above) also have a very similar approach. From its minimum example (I copy it here and add some notes):

tag AppView
    def render
        <self>
            <h1.title> "Welcome"
            <p.desc .red=(Math.random > 0.5)> "Roulette"
                    ^^^^^^^^^^^^^^^^^^^^^^^^ the only expression that may change

It will generate the following code:

class AppView extends Imba.Tag {
  render() {
    var $ = this.$; // inline cache for tag
    return this.setChildren($.$ = $.$ || 
    // Part 1. Initializing Code
    // This portion create elements and init static attributes and content.
    [
      Imba.tag('h1',$).flag('title').setText("Welcome"),
      Imba.tag('p',$).flag('desc').setText("Roulette")
    ]).synced(
    // Part 2. Updating Code
    // This portion update non static values.
      $[1].flagIf('red',Math.random() > 0.5)
    );
  }
}

If I understand correctly, If it is the first render: both Part 1 and Part 2 will be executed. If the elements already exists, then, only Part 2 of the code executes.

In conclusion, the underlying magic of Imba is the way it generates codes to separate static and non static content (and only execute relevant code). It is also how Simi works with the help of procedural macro.

but perhaps it would no longer hold things like text, attributes, children etc.

I am not sure if there is a way to avoid storing these things. I still need to store them (the non static ones) in the virtual DOM.

I took a closer look at Simi. @limira, I am very impressed with your work. Probably the most optimized virtual node implementation in Rust ! Also the docs and the provided tooling makes it super easy to try it out. And it is great to have its author participating in this thread here. However the performance gain (I assume) comes at at cost of much higher complexity, a very different concept than just virtual-node-diffing and the requirement of nightly rust. Well, it is up to @David-OConnor to decide and prioritize, when and how to do virtual node optimization and what role will play Simi or any other existing implementation.

Top priority is getting http requests (ie fetch) working in a high-level API. (eg: data = seed::get('https://test.com', headers), or something async with a callback) No luck so far due to problems with futures. Once complete, this would be more suitable as a standalone crate any bindgen-using framework could use. (Reqwest/hyper etc don't work on the wasm32-unknown-unknown target.)

In Seed's current approach, we lose information that could be used to determine which elements to diff/re-render during the vdom-recreation step. (eg calling the update func on the view func); this new vdom tree doesn't know which info came from a change in the model, and which is static... And I don't see any way to directly diff the model. @limira said he worked around this using proc_macros.

From an information-theory standpoint, the pure vdom approach, while simple/elegant to code, is inefficient, and Simi has done a better job of choosing the minimal amount of computation/rendering to accomplish.

Of note: We could get big performance gains with minimal change to the existing vdom by adding an optional static flag that could passed to element macros, but this is ugly.

Going to start decoupling vdom from the rest of the code. First step is defining a trait that vdom elements will implement. The vdom will use structs implementing this trait, vice the hardcoded dom_types::El. Once complete, I expect another vdom setup to use this trait could be swapped in, or Seed's vdom could be used for another framework implementing this trait.

The latest commit shows my current attempt at decoupling the vdom from the dom_types module, using a trait. It's currently in a broken state due to sizing problems.

To be blunt, this isn't going well. Almost ready to revert to the last release's commit, and abort on this. Loads of errors I'm not sure how to deal with. Would rather put this effort into improving features, docs, etc.

Maybe just put it aside as a branch then you may come back and have another look later?

Good call. Reverted code to a previous commit. Repo is back in working order. This is trickier than I'd guessed; shelving it for now. The attempt is preserved in the commits done over the past week.

I tried out seed and ran into serious performance issues. I basically have 2 components: A query box and then a list of query results. If I have enough query results, each keystroke event for the query box triggers enough logic for a delay of over a second.

So I'd like to help fix this, but I don't know what you had in mind for the VDOM implementation.

The current vdom implementation is simple and easy to reason about, but inefficient. I don't have a strong opinion about the best ways to improve it: Any way to improve efficiency, whether in diffing logic, matching children (eg optional keyed children, something I started work on this morning), an Ember-like vdom alternative, input-field-specific workarounds, grouping re-renders into discrete framerate-based chunks etc.

I suspect the original idea of this issue, creating (or using) a plug+play vdom, is intractable, but there's lots of room for efficiency improvement.

there is also one nice, fitzgen/dodrio

Once that stabilizes, if it's easy enough to integrate (sounds like that's a goal) I'm open to using it.

@rsaccon My two cents: dominator is stdweb based, therefore not a potential candidate

I just found this thread. I just wanted to mention that I plan to move dominator to wasm-bindgen.

However, even after that, I don't think dominator is appropriate for seed, because it uses a completely different design choice (Signals without any DOM diffing).

Agree - didn't realize before that it requires a different app design/API.

Closing this - There don't appear to be any suitable fits for this, now, or on the horizon. If someone identifies one, open a new issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Type1J picture Type1J  路  6Comments

mankinskin picture mankinskin  路  5Comments

bheisler picture bheisler  路  4Comments

TatriX picture TatriX  路  4Comments

bgourlie picture bgourlie  路  4Comments