Introduce Suspense and lazy#1593
Conversation
There was a problem hiding this comment.
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/) |
|
|
||
| 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.
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.
This also allows the recheck of isSuspsend and suspendingComponent, so that will save a lot
There was a problem hiding this comment.
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.
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.
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
marvinhagemeister
left a comment
There was a problem hiding this comment.
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. |
JoviDeCroock
left a comment
There was a problem hiding this comment.
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.
Maybe move to here?
| continue; | |
| return catchErrorInComponent(Error('Missing Suspense'), suspendingComponent); |
There was a problem hiding this comment.
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.
Good catch! This may also fix an issue I was having when working on the devtools where Fragments would break an upwards traversal 👍
marvinhagemeister
left a comment
There was a problem hiding this comment.
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 🎉
JoviDeCroock
left a comment
There was a problem hiding this comment.
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 |
| } | ||
| } | ||
|
|
||
| // TODO: Add a react-like error message to preact/debug |
There was a problem hiding this comment.
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.
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 |
marvinhagemeister
left a comment
There was a problem hiding this comment.
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
SuspenseinsideFragmentmessing up the order of the DOM (Fix incorrect DOM when Fragment has siblings #1605)Suspense/lazyremain part of the core or live inpreact/suspenseorpreact/compat? This would save 137 bytes from core what would only add 50 bytes to core for somebody not using suspenseisSuspense = trueinsidecatchErrorInComponentresulting in 3B saving but also make errors thrown inside_childDidSuspendresult in aMissing Suspenseerror?propTypesonlazy()inpreact/debugSuspenseinpreact/debugMissing Suspenseerror must do the job for now