-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
feat: stream non-essential data #8901
Conversation
@Rich-Harris brought up the nice idea of moving parts of this into |
PR to devalue: Rich-Harris/devalue#58 |
I think it might be weird to introduce a I'd personally find this... export function load({ fetch }) {
return {
post: fetch(url).then(r => r.json()),
lazy: {
comments: fetch(url).then(r => r.json())
}
};
} <script>
export let data;
</script>
<div>{@html data.post}</div>
{#await data.lazy.comments}
<p>loading comments...</p>
{:then comments}
<!-- TODO -->
{:then error
<!-- TODO -->
{/await} ...more natural than this: import { defer } from '@sveltejs/kit';
export function load({ fetch }) {
return defer({
post: await fetch(url).then(r => r.json()),
comments: fetch(url).then(r => r.json())
});
} Case in point: it's not until after I wrote that example that I realised it contains a waterfall. If we do feel like awaiting top-level promises is confusing/a mistake and decide to change it in v2, then we could always add a config setting that opts you into v2 behaviour — we've done that for Svelte in the past. Then you'd just have to be careful about avoiding waterfalls: export function load({ fetch }) {
return {
comments: fetch(url).then(r => r.json()), // streamed, because no `await`
post: await fetch(url).then(r => r.json()) // ready on render, because `await`
};
} |
Released |
Nice! |
I'm not sure I follow — why do we need to track that? Surely all that matters is the promise |
If we stream a promise we don't know if all the |
I think it would be totally fine if we said that you need to access those things before returning from |
I'm commenting on this because I've implemented a version of defer in user land. I would probably publish this library soon but I will be more than happy to deprecate it when this land stable in SK. Currently actual streaming is not supported in the load function so I've had to work around this by using a dedicated endpoint with server sent events but I think I found a decent enough solution (would love some insight on this from some other maintainers, for the moment I've talked about this to Geoff and Antony. So basically here's how my library works, you have to add a provided handle function in hooks.server.ts, than in your +page.server.ts you wrap the load function inside a provided defer function. Finally you access the data through a store (so not with export let data). And that's it.
From a security standpoint the first time you call a load function it generates a secure cookie with a random UUID that uses to just get your events from the server. Here's the link to the repo if you want to take a look: https://github.com/paoloricciuti/sveltekit-defer Currently this works fine with adapter node. |
How will |
I don't think it could ever work...you have to await a promise on the client for defer to have sense so no-js = no ability to await promises. |
Maybe allow an option which makes defer only run on client side navigation. On SSR, it resolves on the server. If it's client side navigation, defer well, defers. |
The problem there is that all SSR in Svelte is currently synchronous, and you can't synchronously tell whether a promise has already resolved. So you would need to write your component in a way that it can accept either direct data or a promise resolving to data. At that point, if you're already doing that, you may as well just also write your |
I'm pretty sure this would work: function deferClient(data) {
return ssr ? data : defer(data)
} |
To be clear, deferring will only apply to server loads, not universal loads. If you return an object containing a promise from a universal load function, its resolved value will be discarded and the promise will be created anew when the universal load function runs in the browser. (You can do this today, by the way — it's just how universal load functions work.) So you wouldn't be detecting whether you're in the browser or not, you'd be detecting whether the server load function is running for SSR or for client-side navigation: // src/routes/whatever/+page.js
export function load({ isDataRequest }) {
return {
criticalData: getCriticalData(),
lazyData: {
whatever: isDataRequest ? getLazyData() : await getLazyData()
}
}
} In your component, this would work whether the data was awaited or not: {#await data.lazyData.whatever}
loading...
{:then whatever}
{whatever}
{:catch error}
{error.message}
{/await} To be clear, I'm not necessarily suggesting you actually do this, just that you can. |
Maybe a stupid remark; I am guessing this should not work in |
It'll work in |
Closes #7213
This implements
the proposedstreaming for server load functions which you can utilize in the same way you can today withdefer
utility (inspired by React Router). At the moment this only works for client-side navigations. To implement this for SSR I see a couple of options. They all start with streaming the initial HTML which contains_deferred_X
strings in place where the promises are that are still pending. The options revolve around how the chunks look like that come in later+page.js
load
functions by nesting the promise.Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm test
and lint the project withpnpm lint
andpnpm check
Changesets
pnpm changeset
and following the prompts. Changesets that add features should beminor
and those that fix bugs should bepatch
. Please prefix changeset messages withfeat:
,fix:
, orchore:
.