New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Suspense and lazy #1593
Introduce Suspense and lazy #1593
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if there's an ErrorBoundary between the lazy
and Suspense
component. Wanted to implement it like this aswell but that's the issue I kept bumping against.
Ideally those warnings are thrown in /debug but can be discussed later.
As to the testing strategy I'm not sure about that
We could add a prop (e.g. We should also investigate how React behaves as Suspense is catching promises in React too. EDIT: Just hacked together a fiddle to check the behaviour of React. Seems React doesn't send promises to user-land I'd propse to not send anything that has a |
While writing the tests one thing came to my attention. When using Suspense in React the suspended component tree doesn't loose state. It isn't unmounted, just "parked". I'm not sure whether this is possible with the current solution. Do you have an idea how to handle this? EDIT: Seems I was wrong on this. Components are re-created in react too (see https://jsfiddle.net/sventschui/3auzjo67/21/) |
That would be something very specific to a two phase render cycle and is not possible at this time as far as I know. I’m also not sure as to what point we want to support this in core since this adds a lot of bytes, it’s already adding 400 at this point. |
I guess most of the bytes come from a huge error message I copied from React. We should replace this with a shorter one or even with just a short-link to a wiki/error description page. Also we might not want to bundle in the The state keeping thing is quite important as it breaks all the tests and compat to React currently. When not having suspense in core we at least should try to make it possible in a user-land extension. That means having catchErrorInComponent extendable/replaceable and something to make the state keeping possible. Honestly the dirties hack that comes to my mind here is keeping it as display hidden in user-land code but that will definitely introduce other issues. For the state keeping we might want to try to flag the direct children of Suspend as suspended, keep them as a prop to Suspense and not call unmount on these. During the next render simply re-run render on these children. Thinking about this makes me think of implementing Suspense not as a component but as code in the core that gets triggered if a component is of type Symbol.for("Suspense") Sorry for my thought flush on my phone. Will look into this a bit deeper today once I get my fingers to my macbook. EDIT: React doesn't seem to keep state neither. I was wrong on this. (see https://jsfiddle.net/sventschui/3auzjo67/21/) |
src/diff/index.js
Outdated
|
||
if (isSuspend && suspendingComponent) { | ||
// TODO: what is the preact way to determine the component name? | ||
const componentName = suspendingComponent.displayName || (suspendingComponent._vnode && suspendingComponent._vnode.type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can move this section into preact/debug
. A Suspense
component without a fallback doesn't seem to make much sense, so we could just always warn on that. This would also keep core small and tiny 🎉
EDIT: Removing that would save 158 B
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also allows the recheck of isSuspsend and suspendingComponent, so that will save a lot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still throw the Missing Suspense
error on the suspendingComponent
.
Would you want to save some bytes and just throw the error at the end of the function instead of the suspendingComponent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What he's saying is to throw it in a hook from preact/debug since it doesn't really matter in production anyway. I also think that is the best approach to maintain our lightweight profile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you guys give me a hint on where I could add this to preact/debug
. I did not find a proper place to add it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really cool start 👍 Left a few comments, let me know what you think 🙂
Thanks for these hints. Will definitely incorporate these once I get closer to a working implementation. The thing that currently bothers me is keeping the state of the suspended component tree (as React does). I was thinking of doing the following:
Can't think of how to achieve the second point. One of you guys got an idea how to solve this? EDIT: It seems I was wrong. React doesn't seem to keep state neither (see https://jsfiddle.net/sventschui/3auzjo67/21/) |
I justed hacked a working solution together to keep state of suspended components 🎉 . Would be cool if you could look into this. I don't really feel confident with the current solution. I'll invest some more time when I get to it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First and forall, congratulations on the already -100B 💯
Looking like an awesome feature to have in all honesty.
Something that I'm thinking about is that it could actually flicker if we don't park DOM (we can test this in demo maybe)
might want to have a test with multiple suspended components in one suspense |
000933a
to
9b5124d
Compare
9b5124d
to
5036536
Compare
@sventschui @marvinhagemeister can you check if this implementation works with Next? I have setup a monorepo project that uses this branch with next and a next plugin similar to @zeit/next-preact. Basically I have copy and pasted their code and used it with this branch. Check it out here: https://github.com/spring-media/preact-next-suspense I have written a bit of a documentation to explain the repo in the README |
@LukasBombach as mentioned in slack: The error is due to an old version of |
component._childDidSuspend(error); | ||
} | ||
else { | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe move to here?
continue; | |
return catchErrorInComponent(Error('Missing Suspense'), suspendingComponent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The continue
results in traversing up the tree. This would break the tree traversing AFAIK
Question: what's the reason for using class Suspense {
componentDidCatch(e) {
if (e && e.then) e.then(() => this.setState({}));
else throw e;
}
} This would mean that folks don't pay for Suspense implementation details if they don't import Suspense. |
Well the naming of the method is irrelevant. It just needs to take precedence over regular Edit: Did not yet check but if renaming saves bytes then I'll rename it |
👌 |
diffChildren(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, c, oldDom); | ||
// Passing the ancestorComponent instead of c here is needed for catchErrorInComponent | ||
// to properly traverse upwards through fragments to find a parent Suspense | ||
diffChildren(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent, oldDom); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! This may also fix an issue I was having when working on the devtools where Fragments
would break an upwards traversal 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love the journey of this PR 🎉 In my opinion it is ready to be merged. I'd just skip the test case with Fragments
(via it.skip()
) because this problem will be dealt with separately in #1605 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As stated by @marvinhagemeister this PR is amazing! Great work on it, just one thing that needs to be added is a test that tests your error message in /debug then the coverall will report 100% again.
@sventschui @marvinhagemeister @JoviDeCroock I can confirm this PR works with Next 8.x using |
src/diff/index.js
Outdated
} | ||
} | ||
} | ||
|
||
// TODO: Add a react-like error message to preact/debug |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this Todo need to be resolved ?
If it is unnecessary, I think it is better to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to tackle this (see TODO in PR description) but currently don't know how to do this without adding too many bytes to core
Oh wait, there is still an issue with this and
|
@LukasBombach I once encountered this issue too. As far as I remember it was caused by context not being set correctly but this should be fixed. I can run your example without any issues |
@LukasBombach a new version for EDIT: published it 🎉 |
@sventschui have you checked your |
@marvinhagemeister @sventschui that update seems to have fixed the error 🎉 PS: I have updated my repo for you to try out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wohoo! Let's do this👍🎉✔️
🎉 😍 |
This introduces Suspense and lazy. There are some open things tough.
EDIT: After the excellent feedback on this PR things seem to be close to finish 🎉 Currently this PR adds 187 bytes gzipped to core.
Open things to do / disucss
Suspense
insideFragment
messing up the order of the DOM (Fix incorrect DOM when Fragment has siblings #1605)Suspense
/lazy
remain part of the core or live inpreact/suspense
orpreact/compat
? This would save 137 bytes from core what would only add 50 bytes to core for somebody not using suspenseisSuspense = true
insidecatchErrorInComponent
resulting in 3B saving but also make errors thrown inside_childDidSuspend
result in aMissing Suspense
error?propTypes
onlazy()
inpreact/debug
Suspense
inpreact/debug
Missing Suspense
error must do the job for now