Skip to content

[lit-ssr] Add defer-hydration protocol for coordinating host/child hydration#1388

Merged
kevinpschaaf merged 10 commits intolit-nextfrom
lit-next-ssr-defer-hydration
Apr 14, 2021
Merged

[lit-ssr] Add defer-hydration protocol for coordinating host/child hydration#1388
kevinpschaaf merged 10 commits intolit-nextfrom
lit-next-ssr-defer-hydration

Conversation

@kevinpschaaf
Copy link
Member

@kevinpschaaf kevinpschaaf commented Oct 23, 2020

Goal: Ensure an SSR'ed LitElement does not enable and hydrate itself before its initial data has been supplied to it from its host (by virtue of its host being hydrated). Since in SSR, element upgrade occurs as a result of CE registration being loaded, and since module loading order naturally occurs leaves-up (by virtue of parents depending on their children), a child will typically run its connectedCallback (which in turn schedules its update) before its host when the application bundle is loaded.

Approach:

  • During SSR opcode generation, inject a <!--lit-node--> marker to denote each custom element. Note, the previous <!--lit-bindings--> marker was repurposed (and renamed) to now indicate any node that either has attribute bindings or is a custom element.
  • During SSR rendering in the custom-element-attributes opcode, if the custom element host stack is >1 deep (meaning the CE is within another CE), then a defer-hydration attribute is emitted on the CE. A new customElementHostStack (distinct from customElementInstanceStack) is introduced to track shadow-dom hosts only (the instance stack tracks every open CE, including light-dom parents)
  • During hydration, when <!--lit-node--> is encountered, in addition to looking for bound attributes, we also look for the defer-hydration attribute after hydrating attribute/property/event/element parts, and if present we simply remove it
  • The hydration-support module patches observedAttributes, attributeChangedCallback, and connectedCallback, such that connectedCallback does not run the LitElement.prototype.connectedCallback if it has a defer-hydration attribute at connected time; in that case, attributeChangedCallback detects when defer-hydration is removed and only then calls the base connectedCallback, causing the element to enable updating and ultimately hydrate itself.

This PR also fixes a latent bug with hydrating attribute parts on void elements: we need to check previousSibling of the <!--lit-node> marker for void elements first, and then use parentElement otherwise (it's guaranteed to be the first node of a parent otherwise).

@github-actions
Copy link
Contributor

github-actions bot commented Oct 23, 2020

📊 Tachometer Benchmark Results

Summary

nop-update

  • lit-html-kitchen-sink: unsure 🔍 -4% - +2% (-1.80ms - +0.93ms)
    this-change vs tip-of-tree

render

  • lit-element-list: unsure 🔍 -2% - +2% (-2.66ms - +2.02ms)
    this-change vs tip-of-tree
  • lit-html-kitchen-sink: unsure 🔍 -3% - +1% (-1.69ms - +0.39ms)
    this-change vs tip-of-tree
  • lit-html-repeat: unsure 🔍 -4% - +2% (-0.67ms - +0.39ms)
    this-change vs tip-of-tree
  • lit-html-template-heavy: unsure 🔍 -1% - +4% (-0.94ms - +2.76ms)
    this-change vs tip-of-tree
  • reactive-element-list: unsure 🔍 -3% - +3% (-2.49ms - +2.18ms)
    this-change vs tip-of-tree
  • updating-element-list: unsure 🔍 -3% - +4% (-1.63ms - +2.03ms)
    this-change vs tip-of-tree

update

  • lit-element-list: unsure 🔍 -2% - +1% (-21.13ms - +9.92ms)
    this-change vs tip-of-tree
  • lit-html-kitchen-sink: unsure 🔍 -4% - +2% (-5.26ms - +2.91ms)
    this-change vs tip-of-tree
  • lit-html-repeat: unsure 🔍 -2% - +2% (-10.02ms - +10.31ms)
    this-change vs tip-of-tree
  • lit-html-template-heavy: unsure 🔍 -2% - +2% (-4.40ms - +3.12ms)
    this-change vs tip-of-tree
  • reactive-element-list: unsure 🔍 -2% - +4% (-23.41ms - +37.40ms)
    this-change vs tip-of-tree
  • updating-element-list: unsure 🔍 -1% - +3% (-1.13ms - +3.75ms)
    this-change vs tip-of-tree

update-reflect

  • lit-element-list: unsure 🔍 -2% - +2% (-19.37ms - +18.61ms)
    this-change vs tip-of-tree
  • reactive-element-list: unsure 🔍 -3% - +4% (-29.11ms - +42.84ms)
    this-change vs tip-of-tree
  • updating-element-list: unsure 🔍 -2% - +1% (-4.61ms - +2.28ms)
    this-change vs tip-of-tree

Results

lit-element-list

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
123.60ms - 126.68ms-unsure 🔍
-2% - +2%
-2.66ms - +2.02ms
faster ✔
22% - 24%
35.30ms - 39.27ms
tip-of-tree
tip-of-tree
123.69ms - 127.22msunsure 🔍
-2% - +2%
-2.02ms - +2.66ms
-faster ✔
22% - 24%
34.80ms - 39.13ms
previous-release
previous-release
161.17ms - 163.67msslower ❌
28% - 32%
35.30ms - 39.27ms
slower ❌
27% - 32%
34.80ms - 39.13ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
1078.49ms - 1096.40ms-unsure 🔍
-2% - +1%
-21.13ms - +9.92ms
faster ✔
6% - 8%
68.18ms - 98.58ms
tip-of-tree
tip-of-tree
1080.37ms - 1105.73msunsure 🔍
-1% - +2%
-9.92ms - +21.13ms
-faster ✔
5% - 8%
60.13ms - 95.43ms
previous-release
previous-release
1158.54ms - 1183.11msslower ❌
6% - 9%
68.18ms - 98.58ms
slower ❌
5% - 9%
60.13ms - 95.43ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
1133.72ms - 1157.89ms-unsure 🔍
-2% - +2%
-19.37ms - +18.61ms
faster ✔
4% - 7%
42.90ms - 81.15ms
tip-of-tree
tip-of-tree
1131.54ms - 1160.83msunsure 🔍
-2% - +2%
-18.61ms - +19.37ms
-faster ✔
3% - 7%
40.81ms - 82.49ms
previous-release
previous-release
1193.01ms - 1222.66msslower ❌
4% - 7%
42.90ms - 81.15ms
slower ❌
4% - 7%
40.81ms - 82.49ms
-
lit-html-kitchen-sink

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
51.84ms - 53.12ms-unsure 🔍
-3% - +1%
-1.69ms - +0.39ms
faster ✔
15% - 19%
9.09ms - 12.05ms
tip-of-tree
tip-of-tree
52.31ms - 53.95msunsure 🔍
-1% - +3%
-0.39ms - +1.69ms
-faster ✔
14% - 18%
8.35ms - 11.49ms
previous-release
previous-release
61.72ms - 64.38msslower ❌
17% - 23%
9.09ms - 12.05ms
slower ❌
16% - 22%
8.35ms - 11.49ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
137.10ms - 140.75ms-unsure 🔍
-4% - +2%
-5.26ms - +2.91ms
unsure 🔍
-3% - +1%
-3.82ms - +1.96ms
tip-of-tree
tip-of-tree
136.44ms - 143.76msunsure 🔍
-2% - +4%
-2.91ms - +5.26ms
-unsure 🔍
-3% - +3%
-4.05ms - +4.54ms
previous-release
previous-release
137.61ms - 142.10msunsure 🔍
-1% - +3%
-1.96ms - +3.82ms
unsure 🔍
-3% - +3%
-4.54ms - +4.05ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
41.82ms - 43.37ms-unsure 🔍
-4% - +2%
-1.80ms - +0.93ms
unsure 🔍
-2% - +3%
-0.87ms - +1.15ms
tip-of-tree
tip-of-tree
41.91ms - 44.15msunsure 🔍
-2% - +4%
-0.93ms - +1.80ms
-unsure 🔍
-2% - +4%
-0.72ms - +1.87ms
previous-release
previous-release
41.81ms - 43.10msunsure 🔍
-3% - +2%
-1.15ms - +0.87ms
unsure 🔍
-4% - +2%
-1.87ms - +0.72ms
-
lit-html-repeat

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
15.12ms - 15.70ms-unsure 🔍
-4% - +2%
-0.67ms - +0.39ms
faster ✔
14% - 18%
2.44ms - 3.23ms
tip-of-tree
tip-of-tree
15.11ms - 15.99msunsure 🔍
-3% - +4%
-0.39ms - +0.67ms
-faster ✔
12% - 17%
2.18ms - 3.20ms
previous-release
previous-release
17.98ms - 18.51msslower ❌
16% - 21%
2.44ms - 3.23ms
slower ❌
14% - 21%
2.18ms - 3.20ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
438.92ms - 454.54ms-unsure 🔍
-2% - +2%
-10.02ms - +10.31ms
faster ✔
27% - 30%
169.32ms - 191.45ms
tip-of-tree
tip-of-tree
440.07ms - 453.10msunsure 🔍
-2% - +2%
-10.31ms - +10.02ms
-faster ✔
27% - 30%
170.33ms - 190.73ms
previous-release
previous-release
619.27ms - 634.96msslower ❌
37% - 43%
169.32ms - 191.45ms
slower ❌
38% - 43%
170.33ms - 190.73ms
-
lit-html-template-heavy

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
77.62ms - 80.93ms-unsure 🔍
-1% - +4%
-0.94ms - +2.76ms
faster ✔
18% - 22%
17.70ms - 21.56ms
tip-of-tree
tip-of-tree
77.54ms - 79.19msunsure 🔍
-3% - +1%
-2.76ms - +0.94ms
-faster ✔
20% - 22%
19.24ms - 21.82ms
previous-release
previous-release
97.91ms - 99.89msslower ❌
22% - 28%
17.70ms - 21.56ms
slower ❌
24% - 28%
19.24ms - 21.82ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
175.15ms - 180.45ms-unsure 🔍
-2% - +2%
-4.40ms - +3.12ms
faster ✔
14% - 17%
29.81ms - 36.33ms
tip-of-tree
tip-of-tree
175.77ms - 181.10msunsure 🔍
-2% - +2%
-3.12ms - +4.40ms
-faster ✔
14% - 17%
29.16ms - 35.70ms
previous-release
previous-release
208.97ms - 212.77msslower ❌
17% - 21%
29.81ms - 36.33ms
slower ❌
16% - 20%
29.16ms - 35.70ms
-
reactive-element-list

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
84.37ms - 87.06ms-unsure 🔍
-3% - +3%
-2.49ms - +2.18ms
unsure 🔍
-2% - +4%
-1.30ms - +3.06ms
tip-of-tree
tip-of-tree
83.95ms - 87.78msunsure 🔍
-3% - +3%
-2.18ms - +2.49ms
-unsure 🔍
-2% - +4%
-1.54ms - +3.60ms
previous-release
previous-release
83.12ms - 86.55msunsure 🔍
-4% - +2%
-3.06ms - +1.30ms
unsure 🔍
-4% - +2%
-3.60ms - +1.54ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
1034.39ms - 1076.97ms-unsure 🔍
-2% - +4%
-23.41ms - +37.40ms
unsure 🔍
-2% - +4%
-24.82ms - +39.37ms
tip-of-tree
tip-of-tree
1026.98ms - 1070.39msunsure 🔍
-4% - +2%
-37.40ms - +23.41ms
-unsure 🔍
-3% - +3%
-32.09ms - +32.65ms
previous-release
previous-release
1024.39ms - 1072.42msunsure 🔍
-4% - +2%
-39.37ms - +24.82ms
unsure 🔍
-3% - +3%
-32.65ms - +32.09ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
1135.96ms - 1189.96ms-unsure 🔍
-3% - +4%
-29.11ms - +42.84ms
unsure 🔍
-3% - +4%
-34.66ms - +40.77ms
tip-of-tree
tip-of-tree
1132.32ms - 1179.87msunsure 🔍
-4% - +2%
-42.84ms - +29.11ms
-unsure 🔍
-3% - +3%
-39.29ms - +31.66ms
previous-release
previous-release
1133.58ms - 1186.24msunsure 🔍
-4% - +3%
-40.77ms - +34.66ms
unsure 🔍
-3% - +3%
-31.66ms - +39.29ms
-
updating-element-list

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
54.72ms - 57.75ms-unsure 🔍
-3% - +4%
-1.63ms - +2.03ms
unsure 🔍
-3% - +3%
-1.66ms - +1.80ms
tip-of-tree
tip-of-tree
55.01ms - 57.06msunsure 🔍
-4% - +3%
-2.03ms - +1.63ms
-unsure 🔍
-3% - +2%
-1.46ms - +1.19ms
previous-release
previous-release
55.33ms - 57.01msunsure 🔍
-3% - +3%
-1.80ms - +1.66ms
unsure 🔍
-2% - +3%
-1.19ms - +1.46ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
126.70ms - 130.23ms-unsure 🔍
-1% - +3%
-1.13ms - +3.75ms
unsure 🔍
-1% - +2%
-1.69ms - +2.95ms
tip-of-tree
tip-of-tree
125.47ms - 128.84msunsure 🔍
-3% - +1%
-3.75ms - +1.13ms
-unsure 🔍
-2% - +1%
-2.94ms - +1.59ms
previous-release
previous-release
126.32ms - 129.34msunsure 🔍
-2% - +1%
-2.95ms - +1.69ms
unsure 🔍
-1% - +2%
-1.59ms - +2.94ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
182.10ms - 186.49ms-unsure 🔍
-2% - +1%
-4.61ms - +2.28ms
unsure 🔍
-2% - +2%
-3.22ms - +2.82ms
tip-of-tree
tip-of-tree
182.80ms - 188.12msunsure 🔍
-1% - +3%
-2.28ms - +4.61ms
-unsure 🔍
-1% - +2%
-2.41ms - +4.34ms
previous-release
previous-release
182.41ms - 186.57msunsure 🔍
-2% - +2%
-2.82ms - +3.22ms
unsure 🔍
-2% - +1%
-4.34ms - +2.41ms
-

tachometer-reporter-action v2 for Benchmarks

@kevinpschaaf kevinpschaaf changed the title [lit-ssr] Add $onHydrationCallbacks queue for coordinating host/child hydration [lit-ssr] Add defer-hydration protocol for coordinating host/child hydration Oct 23, 2020
@kevinpschaaf kevinpschaaf force-pushed the lit-next-ssr-defer-hydration branch from deaa64f to 63ea0c2 Compare October 24, 2020 00:19
Base automatically changed from lit-next-ssr to lit-next October 30, 2020 16:29
@google-cla google-cla bot added the cla: yes label Apr 13, 2021
@kevinpschaaf kevinpschaaf force-pushed the lit-next-ssr-defer-hydration branch 3 times, most recently from 7c862ad to 1dc02df Compare April 13, 2021 09:17
@kevinpschaaf kevinpschaaf changed the base branch from lit-next to lit-next-ssr-wtr April 13, 2021 09:17
sorvell
sorvell previously approved these changes Apr 14, 2021
* # template
* <div class="TEST_X">
* <!--lit-bindings 0--> # Indicates there are attribute bindings here
* <!--lit-node 0--> # Indicates there are attribute bindings here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename this file experimental-hydrate.js

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #1743

@sorvell sorvell dismissed their stale review April 14, 2021 00:38

Need 1 change.

Copy link
Member

@sorvell sorvell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically seems ok, but let's rename the hydrate file to experimental-hydrate.

) {
if (this.shadowRoot) {
this._$needsHydration = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does _$needsHydration need to be set here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

// call the base implementation, which would also adopt styles (for now)
const createRenderRoot = LitElement.prototype.createRenderRoot;
LitElement.prototype.createRenderRoot = function (this: PatchableLitElement) {
if (this._$needsHydration) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should set _$needsHydration here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, updated

@kevinpschaaf kevinpschaaf force-pushed the lit-next-ssr-defer-hydration branch from 050c553 to 95b022b Compare April 14, 2021 08:09
@kevinpschaaf kevinpschaaf changed the base branch from lit-next-ssr-wtr to lit-next April 14, 2021 08:09
if (name === 'defer-hydration' && value === null) {
connectedCallback.call(this);
} else {
attributeChangedCallback.call(this, name, old, value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should always call into the original attributeChangedCallback. What if an element wanted to do something special on hydration?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good call. Updated.

// super.connectedCallback()
const attributeChangedCallback =
LitElement.prototype.attributeChangedCallback;
LitElement.prototype.attributeChangedCallback = function (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be good to put as much of this logic onto ReactiveElement as possible for other subclasses to use...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do that in a later PR, since it involves adding a hydrate-support for reactive-element, which doesn't yet exist.

@kevinpschaaf kevinpschaaf merged commit d459650 into lit-next Apr 14, 2021
@kevinpschaaf kevinpschaaf deleted the lit-next-ssr-defer-hydration branch April 14, 2021 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants