sycamore icon indicating copy to clipboard operation
sycamore copied to clipboard

View Backend v2!

Open lukechu10 opened this issue 1 year ago • 1 comments

Finally, after having gone through a v3 version of our reactivity system with #612, it is time we rework the view system.

This PR eliminates GenericNode that was introduced all the way back in #67 to support both SSR and CSR. Both SSR and CSR are still supported via a different mechanism.

Unlike Leptos, Dioxus, and many other up-and-coming Rust web frameworks, I choose not to use feature flags to differentiate between SSR and CSR mode. This is primarily because doing so would make features non-additive, meaning we cannot enable both at the same time.

In this PR, I have implemented SSR support by introducing a new HtmlNode type that acts a little like a virtual DOM node. Don't let that fool you, however, Sycamore still uses fine-grained reactivity for all DOM updates. Instead, what happens is that our view components constructs a static tree out of these HtmlNodes that describes the initial state of the UI. We then pass this tree to either DomRenderer which will upgrade our tree into actual DOM nodes which can then be modified using reactivity, or SsrRenderer which will directly serialize the tree to an HTML string.

Closes #542

New builder API

Despite having re-worked the builder API in #373 for v0.7 and further small changes for v0.8, the builder API was still not very ergonomic to use. The builder API was always more of a second-class citizen relative to the view! macro.

Before this PR, the builder API was an after-though. Now it is crucial. All HTML attributes are now fully type-checked (incidentally solving #556) via the builder API. The view! macro has also been dramatically simplified to just generating builder methods.

For a taste, here is a comparaison between the old and the new builder API.

// Old
div()
    .c(h1()
        .t("Hello ")
        .dyn_if(
            move || !name.with(String::is_empty),
            move || span().dyn_t(move || name.get_clone()),
            move || span().t("World"),
        )
        .t("!"))
    .c(input().bind_value(name))
    .view();

// New
div()
    .children((
        h1().children((
            "Hello ",
            move || {
                if !name.with(String::is_empty) {
                    span().children(move || name.get_clone())
                } else {
                    span().children("World")
                }
            },
            "!",
        )),
        input().bind(bind::value, name),
    ))
    .into();

Closes #466, #556

Possibly resolved: #604

Hydration

We have DomRenderer for rendering to DOM nodes, we have SsrRenderer for rendering to a static string. And we also have DomHydrateRenderer for hydrating existing DOM nodes. This works differently from the existing implementation by first querying all the nodes with data-hk and storing them inside a list. This gives us a massive performance boost compared to querying every node seperately.

Supersedes #537

Fixes #512 (needs confirmation)

Migration

The main change required is to remove all the pesky <G: Html>s and replace View<G> with simply View. This can easily be accomplished via a simple find-and-replace.

Users of the builder API will need to migrate over to the new syntax. Unfortunately, there isn't much of a mechanical way of doing this other than following the new API,

Some smaller things involve changing iterable to list for Keyed and Indexed components.

Finally, since attributes are now type-checked, invalid uses of attributes will now give a hard error. Another consequence is that since all attributes must be valid Rust identifiers, attributes such as type are now r#type. The special ref attribute is still the same since it is specially handled by the macro.

Remaining tasks:

  • [ ] Fix suspense support
  • [ ] Re-implement attribute spread
  • [ ] Re-implement template optimizations (old implementation in #536)
  • [ ] Re-implement async event handlers (old implementation in #553)
  • [ ] Re-implement stable isomorphic IDs (old implementation in #565)
  • [ ] Add some more abstractions around HtmlNode to make the core View<T> type usable for other renderers (e.g. native, tui, etc...)
  • [ ] Fix router + website
  • [ ] Write docs + tests!

The core work of implementing this new rendering model has already been done so these tasks may be left for other PRs to speed up iteration time.

Also possibly fixed by this PR: #595

lukechu10 avatar Apr 03 '24 04:04 lukechu10

Benchmark Report

  • wasm-bindgen: the performance goal
  • baseline: performance of sycamore-baseline (typically latest master)
  • update: performance of sycamore (typically recent changes)
  • diff: measures the improvement of update over the baseline
@@                         Performance Diff                          @@

##                       | wasm-bindgen | baseline |  update |  diff ##
#######################################################################
- 01_run1k               |         8.92 |    19.17 |   32.01 |  +66.99%
- 02_replace1k           |        14.49 |    30.41 |   45.12 |  +48.36%
+ 03_update10th1k_x16    |         1.60 |     3.18 |    2.99 |   -6.08%
- 04_select1k            |         0.44 |     5.12 |    5.68 |  +10.77%
- 05_swap1k              |         0.61 |     1.78 |    2.06 |  +15.73%
- 06_remove-one-1k       |         0.27 |     1.13 |    1.28 |  +13.61%
- 07_create10k           |       102.57 |   213.02 |  345.21 |  +62.05%
- 08_create1k-after1k_x2 |         8.62 |    19.49 |   35.14 |  +80.27%
- 09_clear1k_x8          |        23.69 |    27.51 |   39.28 |  +42.80%
- 21_ready-memory        |         1.66 |     1.65 |    1.70 |   +3.17%
  22_run-memory          |         2.82 |     4.92 |    5.05 |   +2.69%
- 23_update5-memory      |         2.86 |     4.93 |    5.15 |   +4.50%
- 25_run-clear-memory    |         1.76 |     3.53 |    3.85 |   +9.06%
+ 26_run-10k-memory      |        13.38 |    41.70 |   33.19 |  -20.41%
+ 41_size-uncompressed   |        47.00 |   276.70 |  267.90 |   -3.18%
+ 42_size-compressed     |        14.50 |    66.60 |   64.50 |   -3.15%
+ 43_first-paint         |       119.80 |   506.60 |  488.10 |   -3.65%

Workflow: 8547975018 Adding new commits will generate a new report

github-actions[bot] avatar Apr 04 '24 02:04 github-actions[bot]