Skip to content

Hydration Suspend & Resume#2754

Merged
marvinhagemeister merged 5 commits into
masterfrom
hydration-suspend-resume
Sep 15, 2020
Merged

Hydration Suspend & Resume#2754
marvinhagemeister merged 5 commits into
masterfrom
hydration-suspend-resume

Conversation

@developit

Copy link
Copy Markdown
Member

This PR implements suspend & resume for both hydration and new tree construction. If rendering throws during hydration or new tree construction (first render of a component or tree), catching the error up the tree in something like an "Error Boundary" will attach the hydration state and available DOM tree to the boundary's VNode. Triggering a render on this node (via any mechanism) attempts to resume from this state. The throw/catch process can be repeated and will continue to preserve DOM state. Once rendering completes, affected VNodes are un-marked and the behavior no longer occurs.

@github-actions

github-actions Bot commented Sep 14, 2020

Copy link
Copy Markdown

Size Change: +367 B (0%)

Total Size: 40.5 kB

Filename Size Change
compat/dist/compat.js 3.17 kB +30 B (0%)
compat/dist/compat.module.js 3.18 kB +27 B (0%)
compat/dist/compat.umd.js 3.23 kB +30 B (0%)
dist/preact.js 4 kB +71 B (1%)
dist/preact.min.js 4.03 kB +71 B (1%)
dist/preact.module.js 4.02 kB +68 B (1%)
dist/preact.umd.js 4.07 kB +70 B (1%)
ℹ️ View Unchanged
Filename Size Change
debug/dist/debug.js 3.09 kB 0 B
debug/dist/debug.module.js 3.09 kB 0 B
debug/dist/debug.umd.js 3.18 kB 0 B
devtools/dist/devtools.js 185 B 0 B
devtools/dist/devtools.module.js 195 B 0 B
devtools/dist/devtools.umd.js 261 B 0 B
hooks/dist/hooks.js 1.1 kB 0 B
hooks/dist/hooks.module.js 1.12 kB 0 B
hooks/dist/hooks.umd.js 1.18 kB 0 B
test-utils/dist/testUtils.js 437 B 0 B
test-utils/dist/testUtils.module.js 439 B 0 B
test-utils/dist/testUtils.umd.js 515 B 0 B

compressed-size-action

@coveralls

coveralls commented Sep 15, 2020

Copy link
Copy Markdown

Coverage Status

Coverage remained the same at 100.0% when pulling 37fecbf on hydration-suspend-resume into 9aa7d38 on master.

@marvinhagemeister marvinhagemeister left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Love this 👍

@marvinhagemeister marvinhagemeister merged commit d76742c into master Sep 15, 2020
@marvinhagemeister marvinhagemeister deleted the hydration-suspend-resume branch September 15, 2020 07:12

@andrewiggins andrewiggins left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know I'm a little late to the party 😅 but left a couple thoughts from an initial read for a later PR. I suspect when we add some tests we'll have some tweaks to make so things can be addressed then.

Comment thread compat/src/internal.d.ts
error: Promise<void>,
suspendingComponent: Component<any, any>
suspendingComponent: Component<any, any>,
oldVNode?: VNode

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can remove this - I didn't see anywhere where an implementation of _childDidSuspend took a third param

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I wasn't sure if we needed to add it in order to fix the issue you mentioned about picking the wrong vnode on which to check _hydrating.

Comment thread compat/src/suspense.js
* to remain on screen and hydrate it when the suspense actually gets resolved.
* While in non-hydration cases the usual fallback -> component flow would occour.
*/
const vnode = c._vnode;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I still need to run this code to verify my understanding, but my first reading through makes me think this may need to be suspendingComponent._vnode since we set vnode._hydrating on the vnode that threw the error (diff/index.js:275), not on Suspense._vnode (though we could in Suspense's _catchError implementation at the top of this file).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You may be right. I need to clarify which vnode(s) we want to set the _hydrating bit on. My preference would be to set _hydrating on the vnode for the component that caught/handled the suspend, since technically we bail out of the tree at that point and must resume from that point.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah - yeah currently we're setting it on both the bailed-out VNode and the VNode for the component that handled the bailout:
https://github.com/preactjs/preact/pull/2754/files#diff-b5e3843a998fbd0144a6ac5b1b7536a4R33

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yea, that makes sense to me (setting in on the place in the tree where we bail out and resume). Though I think when using compat's Suspense, the _catchError code isn't run since Suspense ships it's own _catchError implementation that doesn't have these changes to set hydrating, but that'd be trivial to add.

Looking back at it, I'm don't remember why we didn't just use componentDidCatch to implement Suspense... Maybe we needed something like your handled logic but no one tried that at the time?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the reason for not using componentDidCatch was that some users were confused why the received a Promise in their error handlers instead of an Error object. We originally implemented it that way iirc, but backpedaled.

Comment thread src/internal.d.ts
_nextDom: PreactElement | Text | null;
_nextDom: PreactElement | null;
_component: Component | null;
_hydrating: boolean | null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps this wouldn't compress as well since we deal with nulls way more than booleans, but just leaving a note for myself (cuz I'm curious) to explore pros/cons of just making this a normal boolean that is initialized to false and flipped between true/false as appropriate.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@andrewiggins this needs to be a tri-state though:

  • true: the vnode has DOM from bailout during hydration
  • false: the vnode has DOM from bailout during initial render (new tree creation)
  • null: the vnode has no DOM from bailout

We could use an int though, that's probably much better for perf.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, I missed that false case. Makes sense

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants