-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Svelte suspense (request for comments) #3203
Comments
Perhaps my misunderstanding, but I understand the key benefit of Suspense to be the inversion of control (children control the loading/entering the suspense state). It isn't difficult today to conditionally render child content based on whether its loaded, to show previous state, or a loading indicator on a cancelable timer. You can always push that to the parent to achieve this effect. What makes Suspense interesting is the parent goes "Here's a placeholder. Children do whatever you need to do, I don't even need to know what exactly. I'm here to support you and make sure you don't embarrass yourselves." We can probably avoid throwing Promises, but I think we need to at minimum execute the children without showing them if necessary. This makes everything more complicated like conditional rendering in between the Suspense placeholder and the asynchronous call. But that's the problem we need to solve.Thats what makes this interesting. |
@ryansolid I would agree with most of that. Suspense in React is generic in the sense that its behaviour does not depend on its children. Children indeed signal (through promise sending) something to their parent, with the parent deciphering the meaning. For now it is just a 'not ready' signal but it could be anything, this is really a communication children - parent that is occurring here. I know only three ways of doing that: shared state, events, and passing continuations (callbacks, coroutines etc.), each with its own set of tradeoff. The One advantage of throwing a promise is that it seems to magically remove the necessity for a communication interface, e.g. there is no need to inject anything in the child as
That exactly what I do. Children are wrapped in a Here is the displaying code: <style>
.incognito {
display: none;
border: 0;
padding: 0;
margin: 0;
}
.cognito {
display: inherit;
border: 0;
padding: 0;
margin: 0;
}
</style>
{#if stillLoading }
<slot name="fallback" dispatch={next} intents={intents} ></slot>
{:else if errorOccurred }
<slot name="error" dispatch={next} intents={intents} data={data}></slot>
{:else if done }
<slot dispatch={next} intents={intents} data={data}></slot>
{/if}
<div class="incognito">
<slot dispatch={next} intents={intents} ></slot>
</div> This behaviour is illustrated in the example I gave: <Suspense let:intents={{done, failed}} timeout=0>
<div slot="fallback" class="album-img">
<img alt="loading" src="https://media.giphy.com/media/y1ZBcOGOOtlpC/200.gif" />
</div>
<a href={link} target="blank" class="link">
<img class="album-img"
on:load={done}
src={image}
alt={'itunes' + Math.random()} />
</a>
</Suspense> The That brings me to the last point which is about another advantage of the
|
@ryansolid After rereading your message, actually I think I do not understand that part:
In most of the suspense example I have seen, the loading state is entered immediately ( Anyways, in the Suspense component I propose, a task if any is run on mounting the children, and the results (if any) of this task are passed to the children. A different logic is easy to implement by just changing the state machine. The whole control flow logic is embedded in the machine. In fact, if you remove the |
It's entered immediately often (although not always), but the child is responsible for loading the data. So even if there is a cache etc, it's the execution of the child that actually does the async request etc. The Suspense element itself is above it in the hierarchy(usually a different component) and isn't aware of the specifics. So when the execution starts in the parent and you execute the Suspense element your aren't suspended, but when the downstream child is executed and does an async request that is the point at which it is realized. So from that perspective, the image loading example holds up, but the data loading one where the parent doing the fetch is too coupled I think. The original example for React I believe was roughly the following. Pretend we have a Component on the page that has a navigation bar and a main content section. As I change the navigation the content changes and may or may not have different async data needs. On a change of navigation I want to show the existing content until the new navigation has fully loaded, and if it takes too long show a spinner. So we implement this by wrapping the content section with our Suspense Component. Lets say after 300ms it shows a spinner until the section fully loads. So say one child loads feeds, one loads albums. We start by rendering the feeds page. It realizes that we need to do a data load and triggers Suspense. When it is complete we see the feeds content. When the user clicks "Albums" we update the navigation highlight (perhaps with a sliding animation that takes 300ms). It triggers the navigation change loading the Albums Component doing a different data load triggering the parent Suspense. However we still show the Feeds content. If it takes over 300ms we show we replace it with a spinner otherwise if less we replace the Feeds content with the Album content. At no point did we flash the page blank for 100s of ms or load an unnecessary spinner. I think your approach could handle this. But the key is that the child have different data needs (one loads feeds, one loads albums) so the handling of that belongs with the Child Components and not with the parent container. |
What you describe is the following flow:
Up to the last part it is fairly easy to implement, this is the standard event-action paradigm which characterizes reactive systems. The last part however does not have the form Instead of routing let's consider a master-detail navigation. We have the following component tree rendered at <A>
<B>
</A> User clicks at <A>
<B /> // Master
<C invisible /> // Detail
</A> Fetch is successful: <A>
<B />
<C />
</A> In this case, we are covered because we only add to the existing hierarchy. So the DO NOT REMOVE STUFF FROM DOM part is already the normal rendering behaviour. I mean by that what is displayed by If we talk about routing, from <A /> User clicks at <A /> Fetch Succeed event before Timer Expired event: <B /> Timer Expired before Fetch Succeed arrives: <Spinner /> I confess that I have no idea what is the behaviour of Svelte with respect to routing. Outside of Svelte, there are routing libraries which have asynchronous transitions, and that could probably implement the sought out functionality. But the functionality would be coupled to the router, so probably the fetch would have to be in the router, not in the component. In the same way, the Suspense functionality is integrated into React (because Error Boundaries are integrated into React), |
Yeah, I was thinking as much. Any conditional(routing) in between the Suspense placeholder and Child Component Resolution would have to be Suspense aware. If this was solvable, I think pretty much any suspense case would be. If the Suspense API here was standardized for Svelte this could be something a Router provider could build in if they could observe the same state. Mind you they would need to run multiple paths at the same time. Without switching the display necessarily. React's versions is absolute in that even changes to the current Component would not track during that period (since they interrupt rendering) but that might be unnecessary in practice. I also wonder React's Suspense is just a restrictive implementation of a subset of the use cases here and the real feature looks a little different. Especially when put against a different system (like Svelte). In any case I think we understand each other. I think the Routing case can probably be a future consideration. The key part for me is is that any data fetching mechanism should be triggerable from below. Separating the display handling of fallbacks from the details of what goes on below. |
Actually I am thinking that maybe this could be solved if Svelte has a special NULL value for templates. e.g. when a template evaluates to empty, then don't update the DOM (I believe the current behaviour would be to detach the relevant pieces DOM). So if from
If I dare say, that would be analog to encoding exceptions in a return value instead of throwing. |
Excellent point and it helped me think more clearly about a presentation I had to give on suspense and concurrent mode.
What's interesting is that the inversion of control to children does not necessarily mean the children initiate the promise they are waiting on. That was emphasized more in their additional blog post: In the case of data fetching, they don't necessarily trigger the data fetch. They just check on the state of that request (such as calling So how do they maintain this inversion of control then? How do parents stay agnostic of children data dependencies to maintain loose-coupling/maintainability/etc.? In the case of Relay and GraphQL, the components declare the "fragments" of data they need and then Relay analyzes them at build time and aggregates the fragments into top level queries that are kicked off on routing changes, etc. The initiating of the data fetch promise(s) is built into the router. So children's data dependencies are still colocated with the component, but not necessarily the fetching of that data. They also have a REST proof of concept: https://github.com/gaearon/suspense-experimental-github-demo In that example, the components have separate data files that allow them to colocate data dependencies. However, the top level aggregated parallel fetch on route change is not generated at build time and is instead written by hand. Example: https://github.com/gaearon/suspense-experimental-github-demo/blob/master/src/UserPage.data.js But in theory, they could be generated as well. If not, then you lose some of that loosely-coupled inversion of control. But if you generate them at build time like the Relay case, maybe not?
I haven't gone through the relay docs and example app yet to fully understand how it works. I'm also curious about things like conditionally rendered components inside suspense boundaries and how that would work with these generated aggregated queries for a tree of components. |
Yeah I did not understand that at the time but they definitely do not need to do the async request themselves just trigger Suspense on read. This wasn't as clear before they released the more recent docs for data-loading. Since last posting to this conversation, I've worked through designing a Suspense pattern that can be mimicked with a reactive library and I've implemented them in my reactive UI library(SolidJS). The first piece comes down to understanding how rendering works in Svelte, as rendering has to be non-blocking. We need to ensure that Components are still able to go through initialization code without being visible. In Solid's case I do it before attaching to the DOM, but if we need to mount with Svelte it can still work by controlling visibility. The next is having a means to setup reading of a Suspense state. I ended up using a Context API mechanism to do that making the Suspense Component essentially a Provider. From there specialized loading methods can read into that context to maintain suspense state. I am unsure of an easy way to handle Finally all control flow directives in Suspense boundaries need to be aware of this same context. From there it is fairly easy to hold changes. It might make sense for this behavior to be opt in (some syntax addition) to allow better tree shaking since Suspense boundaries can be cross Component meaning that the compiler probably will have difficulty predicting this completely from the compilation step. I assume the Svelte solution would need to be a little different but the fundamentals should be similar. If its any help the latter half of my medium article might shed some clues I'm not thinking of at the moment. This conversation helped me take the first steps along this path and I'd love to help any way I can. |
@sschottler While I understand the general principles behind React Concurrent Mode, I am not sure I understand in the details how it works. Splitting work into chunks, and giving back the hand to the browser for user event handling obviously improves the reactivity of the application. What I don't understand is how the back pressure issues that it creates are solved. Conceptually this is similar to a search-as-you-type field. Any field update triggers a new query, and then responses come at any time and in any order. So if you want to display responses in-order, in the worse case (the response for the first query comes last), you have to buffer as many responses as there were queries. In the real world, you would drop the responses that do not correspond to the current query - which correspond to lossy back-pressure. The analogy with Concurrent Mode is that user interactions may generate new render queries. It is not clear to me whether React discards the vDOM it was computing (no longer current) to focus on the new vDom triggered by user interaction; or do both vDOM computation in parallel, and displays (commit phase) them individually in order when computation is finished; or display what it has computed so far on every requestAnimationFrame; or does a merge between vDOM version a-la git; or ... But that gets up away from the OP's topic. Indepedently of the specific techniques used, I think the API for Suspense is worth meditating on. As I said in a previous comment, some form of communication children-parent will have to be involved, and I can think of three ways to do that: shared state, events, and callbacks. So @ryansolid seems to be using shared state (context). I used callbacks in my previous Svelte examples (callback which use events under the hood). The point is while the technique may differ, it will be possible to implement suspending functionalities no matter what the framework/library is. The issue for me is thus the API. My preferred component organization is to have component with a single concern. That means presentational component, render-less or logic component, layout components, etc. Suspense enters in the category of the logic component and ideally its children should be presentational component. Now, lets review the great examples of @ryansolid:
<Suspense fallback={<Loader />}>
<AsyncChild start={startTime} />
<AsyncChild start={startTime} />
<AsyncChild start={startTime} />
<AsyncChild start={startTime} />
<LazyChild start={startTime} />
<LazyAsyncChild start={startTime} />
</Suspense> This is a good example, because the suspense logic resides in the Suspense component. However there is not a complete separation achieved here because the code for the children components would change (however little) if no suspense functionality was used, e.g. <AsyncChild start={startTime} />
<AsyncChild start={startTime} />
<AsyncChild start={startTime} />
<AsyncChild start={startTime} />
<LazyChild start={startTime} />
<LazyAsyncChild start={startTime} /> But on the surface of things, it looks good enough.
<Suspense fallback={<Loader />}>
<Switch transform={awaitSuspense}>
<Match when={state.tab === 0}>
<AsyncChild page="Uno" />
</Match>
<Match when={state.tab === 1}>
<AsyncChild page="Dos" />
</Match>
<Match when={state.tab === 2}>
<AsyncChild page="Tres" />
</Match>
</Switch>
</Suspense> This is also a terrific example, where There again if I wanted to discard the suspense implementation detail, I can just remove the
const App = () => {
const resource = fetchProfileData(0);
return (
<Suspense fallback={<h1>Loading...</h1>}>
<ProfilePage resource={resource} />
</Suspense>
);
};
const ProfilePage = ({ resource }) => (
<SuspenseList revealOrder="forwards" tail="collapsed">
<ProfileDetails user={resource.user.value} />
<Suspense fallback={<h2>Loading posts...</h2>}>
<ProfileTimeline posts={resource.posts.value || []} />
</Suspense>
<Suspense fallback={<h2>Loading fun facts...</h2>}>
<ProfileTrivia trivia={resource.trivia.value || []} />
</Suspense>
</SuspenseList>
); Same comments, the difference is that the fetch is the at the top level So the questions for me here are two:
|
Ok first on Concurrent Mode. React uses their own scheduler but essentially it's a play on This repo helped me understand the potential of different scheduling mechanisms: https://github.com/justinfagnani/queue-scheduler That being said this is no silver bullet and has tiny bit of overhead on normal operation even when able to use the idle time as now all updates are asynchronous. I implemented this mechanism in a demo by essentially by passing in reactive var and creating a new reactive var that basically acted like a buffer. Then I'd queue up reading the first reactive variable to set the 2nd in a computation if it wasn't already scheduled. It's a cool demo but I think it's really inconsistent thing to generalize. Quite often skipping this is just so much faster it doesn't matter. Without having to deal with Top Down reconciliation in a reactive library the gains aren't often as big. I see some uses for introducing this sort of scheduling but I think it probably needs to be opt in or deliberate unless you want performance across the board to take a bit of a hit. Sort of a self fulfilling prophecy if not done perfectly and I think that is an area that needs way more research. But luckily for a reactive library we can do Suspense without Concurrent Mode since we already have that granularity at our disposal. To your questions. I'm not sure you can always make the child unaware. I set up solid so that the Suspense aware helpers like But can you do similar with Svelte's compiler/reactive system? Probably, but not 100% sure. Even React requires the child component to be aware they are reading from a resource (special object). Svelte does have reactive props that can be passed down so if there is some way to hide it in there I believe it can work the same way. |
I asked Kent C Dodds about this: His post made me think some of this scheduling happens automatically, but in my simple experiments, I've had to opt in by wrapping my state changes in |
Yeah I wonder too. They've built a queuing system if you look at the source code that they could apply anywhere. They basically polyfill |
React core team member responded regarding whether concurrent mode requires opt-in: |
Is it worth turning this into a RFC (https://github.com/sveltejs/rfcs)? |
Either I am dramatically misunderstanding the problem, or this conversation has gotten quite sidetracked from the original goal. It looks like we can do a very good approximation of Suspense with existing infrastructure. This lets us write code like: // parent.svelte
<Suspense>
<Loading slot="loading" />
<Child />
<Child />
</Suspense>
// child.svelte
<script>
import { createSuspense } from 'suspense'
const suspend = createSuspense()
const data = fetch('/something')
</script>
{#await suspend(data) then item}
{ item }
{/await} without the consumer passing data around. The child component, or any of their children components, can dictate when loading is finished. There are two limitations I've found so far that would require Svelte itself to makes changes to overcome:
Both of these could be solved by adding a |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yeah a lot has changed in understanding (and even the React implementation) since the original post was made. I think things have been better defined now and Svelte should be able to better make a decision where to go here. Vue/Preact decided to not follow React all the way down the concurrent mode rabbit hole. In fact, what originally was considered a single thing is now arguably a couple different but related features.
The idea here is that in order to maintain async consistency we can't let an in flight change be visible outside the scope of that change. A more concrete example might be picturing a "Like" button on a User information carousel, where you click next to load the next user. In the ideal world proposed here, one could click next starting the rendering and loading of the next user off screen, and then the enduser can click like button while this is happening and still have the current in view user record be the one that is liked as the updated user id won't have propagated yet. Basically both rendering the next possible state (to perform data loads), while showing the current state that is completely interactive without causing any inconsistencies both in data or in visual tearing. Ie.. the page number doesn't update until everything is completed. This is the benefit of transitions. Now React has refined this further stating that even under transitions it is only previously rendered Suspense boundaries that hold, and newly created ones instead fall to the fallback state (ie any async reads under them are not counted towards the transition). In so the enduser has the ability to create the boundaries and nested boundaries of where you want to hold or show placeholders while it frees up things like routers or design systems to just blindly implement transitions into their controls. If those transitions never trigger something async that is read under a Suspense boundary they are basically identity functions that do nothing. However if the application implements Suspense they tap into a much richer system for controlled orchestration of both placeholders and held(stale) states. This seems complicated and it is, but the introduction of stale states save from excessive falling back to loading placeholders. You can picture with tab navigation if you are already looking at a tab and click the next one you don't need to replace the whole view with a placeholder.. a simple indicator of the stale state would be much less jarring. Browsers natively do this on page navigation. Even if a page is slow to load if it were server-rendered (ie the HTML all present when it does load) it skips ever showing the white screen. You see the current page, then some sort of loading indicator and then bam the new page. We are less accustomed to this these days because of all the client-side rendering going on and forced loading states. But the native browser handles this pretty seamlessly.
So I know reactive frameworks like Svelte (and I've made the same wager with the reactive frameworks I am maintaining) don't need transitions to get this sort of rendering. I already have it working in both of mine without (even though I have implemented transitions in one and intend to in the other). So the 3rd feature I think is unneed here. Analysis: If desirable it's probably fine to go ahead with 1 and forget about it. Mind you it is a lot less interesting for a library that already has an Suspense is a bit slicker to be sure but most of that comes in actually burying the promise in a resource primitive when it comes to data loading. You can basically treat it like a typical Svelte store and not even write the clearly async The second feature is mind warping and really what people have been referring to when talking to this in the past. Rich in an issue a while back was talking about how to encode change sets as a way to achieve this. I've been down that road too but ultimately decided that forking the reactive primitives into transactions(while holding updating existing side effects) is probably the best way to achieve this in a reactive system. But let's face it, again this sort of quantum reactivity is a steep price for basically a navigation trick. It's a freaking cool navigation trick but you have to ensure large parts of your system are side-effect-free. This is all the grumbling you hear about the concurrent mode in React. There are other challenges being a compiled language. It's doable with dedicated reactive primitives but for a language it is a bit trickier. You could reserve syntax for it. Like $: with await's in it could basically create the new primitives under the hood but would you really want to introduce the equivalent of the |
I'm assuming #2 and #3 are referring to React's page on Concurrent UI Patterns? I took a stab at implementing the main features from their demo there. While it isn't production worthy, I did walk away with a very different idea of what Svelte's blocking issues are. My biggest take away is that Svelte already has a primitive to show two different versions of a DOM tree at the same time: Limitations As noted above, that demo isn't production worthy and I'm pretty sure Svelte would need new features to make it so. Here's a few issues it has:
This entire demo does feel like an abuse of the current transition system. My point isn't that this should be a final API, merely that the underlying components needed to build a good API are mostly already present. The new parts required, while not easy, don't look particularly ground breaking and have prior art. |
A weekend of tinkering, and I have something that just more or less just straight up works: Warts
Edit: Now that I have this working, I can't find a good use case for the Transition component. Every example I've found of React's useTransition, which is what it mimics, has effectively been just for routing purposes. We could implement a router using just Suspense and without half of the weird edge cases this Transition component creates. I'm still heavily in favor of a |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Nope |
Including something like this in Svelte core would have the advantage of making it much easier to do async SSR in general; one of the current obstacles is determining when a page is "complete" and ready to be sent, but the
|
I think this is fair. The navigation story is like 90% page/tab navigation. So in those scenarios there are ways for the router to render offscreen. Transitions are are sort of like the academic solution for purity. I implemented them both ways in Solid over time. Originally just using Suspense and tapping into control flow and eventually moving to forked realities and merging. Since I had that I went the full way to doing interruptible concurrent rendering. The time-slicing element is interesting but I haven't had many applications for it yet. People will point to our performance as a reason not to bother with concurrent rendering and they aren't far off. This whole thing is about asynchronous consistency. So it is all in an effort to prevent glitches and tearing. Without framework intervention it is hard to do since scheduling will slice things the other way and not hold executing side effects. I don't think this would be easy to do with RxJS or any external driver in a generic way. It isn't about scheduling a slice of reactive updates. You really do need to basically clone things. But the problem might be too narrow to warrant a generic solution. It also doesn't help the expense of side effects. It's funny it is always demo'd with like expensive visuals or 3d but it can't help you render faster. At some point you take the hit, and if you want to avoid tearing you take the full brunt of it. This whole mechanism is for expensive pure computations. Think I love how cool it demos, but I think it takes a real specific sort of problem for this because, it isn't the DOM cost which is often the heaviest thing when doing large navigation. DOM creation and attachment doesn't get interrupted. It's if the user code is doing some really expensive stuff and blocking. Is that worthy of systematic solution? I don't know. |
Did anyone from the Svelte team every comment on this issue since 2018? Would be great to get something like this implemented natively. 😀 J |
Is there any plans for including |
Now that we're in the land of signals alongside Solid.js and all, any thoughts on Suspense from the Svelte team? Specifically with regards to the ability of being able to conditionally pause effects/renders/updates from a component subtree, this is where I've found Suspense to be the most beneficial with. Take for example, mobile app navigations, they are stacked and ideally you don't want to rerender any views that aren't currently on screen, but you can't unmount them or else you lose all the states within them, not unless they're stored within a global state (which creates additional complexity). This example is very much real, this is how Suspense is being used within React Native apps, and could end up benefitting Svelte Native too. Other sorts of navigations like tabs, they all benefit from being able to keep offscreen components mounted, yet frozen such that UI updates don't affect them until they are no longer frozen. Personally I think Suspense is a very beneficial thing to have, just not in a manner that React thinks it's beneficial for. |
This would be a nice performance enhancement! other frameworks like Angular are introducing new approaches to get this feature on its primary tools |
It would be amazing to see the Suspense mechanisms come to Svelte 5! +1 for this :) |
After reading this issue, I came up with a Suspense component for Svelte, replicating the behaviour of React Suspense. No React Cache, no throwing promises, no modifying your component to fit a use case, just Svelte component composition. A demo is available in the corresponding GitHub repository. Note that I could not have the demo running in Svelte REPL due to some issues with loading the
axios
package.The behaviour of the Suspense component is implemented with the Kingly state machine library. The summary of 'findings' can be found here. For info, here is the underlying state machine specifying the suspense behaviour:
The demo showcases the API and I will quickly illustrate it here. The demo consists of loading a gallery of images. The suspense functionality is applied twice: when fetching the remote data containing the image URLs, and then for each image which is subsequently downloaded. While the remote data is fetched, a spinner will display if fetching takes more than a configurable time. Similarly, images placeholder will also display a spinner if downloading the image takes more than a configurable time.
Firstly, the suspense functionality for the remote data fetching is implemented as follows:
Note that the fetch task and minimum time (
timeout
) before displaying the spinner is passed as parameters of theSuspense
component, while the fetched data is exposed to the slot component through thedata
property. Note also how the fetching function is passed thedone
andfailed
callback to signal successful completion or error of the remote fetching.The fallback slot is displayed when the timeout is expired. The error slot is displayed when fetching the data encounters an error.
Secondly, the
Album
component suspense functionality is implemented as follows:This time the
Suspense
component passesdone
andfailed
callbacks to its children slots. When the image is loaded, thedone
callback is run.This works well and I believe the API separates well the suspense functionality or concern from the slots. What we basically have is parent and children components communicating through events, except that the event comes included in the callback. As the demo shows, there is also no issues with nesting
Suspense
components.This GitHub issues has two purposes:
The first point is more about hearing from you guys.
About the second point:
being able to operate on the slot as if it were a regular html element. This mean the ability to style a slot with classes or possibly other attributes (Add some extra attributed to cover generic needs, i.e. needs that are independent of the content of the slot. To implement the suspense functionality I had to resort to hide the default slot with<slot class='...' style='...'> </slot>
).display:none
. Unfortunately to do that I had to wrap the slot around adiv
element, which can have side effects depending on the surrounding css. A syntax like<slot show={show}> </slot>
would have been ideal. After thinking some more, I think that slot cannot be considered as regular HTML elements but as an abstract container for such elements. The operations allowed on slots should be operation on the container, not on the elements directly. Styling or adding classes an abstract container does not carry an obvious meaning, as the container is not a DOM abstraction. The current operations I see existing on the container areget
(used internally by Svelte to get the slot content),show
could be another one. The idea is that if you have a Container type, and a Element type, your container isC<E>
. If you do operations that are independents ofE
, you can do only a few things like use E (get
), ignore E (don't use the slot), repeat E (not sure how useful that would be), conditionally use E (show
, of type Maybe). Using any knowledge about theE
I think leads to crossing abstraction boundaries which might not be a good thing future-wise.Suspense
component, I useif/then/else
to pick up the slot to display, which works fine (see code below). It would be nice however to have<slot name={expression ...}>
:I am not really strong about the dynamic slots. It might add some complexity that may be best avoided for now. The first and second point however I believe are important for abstraction and composition purposes. My idea is to use Svelte components which only implement behaviour and delegate UI to their children slots (similar to Vue renderless components). Done well, with this technique you end up with logic in logic components, and the view in stateless ui elements.
The technique has additionally important testing benefits (the long read is here).
For instance the behaviour of the
Suspense
state machine can be tested independently of Svelte - and the browser, and with using a state machine, tests can even be automatically generated (finishing that up at the moment). Last, the state machine library can compile itself away just like Svelte does :-) (the implementation is actually using the compiled machine).About testing stateless components, Storybook can be set to good purpose. What do you Svelte experts and non experts think? I am pretty new with Svelte by the way, so if there is any ways to do what I do better, also please let me know.
The text was updated successfully, but these errors were encountered: