Top-level await in script and / or nuxt-like server data fetching
See original GitHub issueDescribe the problem
The actual way SvelteKit loads data from server before loading the page has two defaults:
- it’s very verbose 🗣️
- it’s not type-safe ☠️
This is a typical example of server-side rendering with SvelteKit:
<script context="module" lang="ts">
export async function load() {
return {
props: {
foo: await fetch("api/foo") as string
}
}
}
</script>
<script lang="ts">
export let foo: string
</script>
So we’ve written a lot of code just to return a string, and it is absolutely not safe. You have to indicate the type first in the load function and then in the main script section.
Linters won’t complain if we write something like this:
<script context="module" lang="ts">
export async function load() {
return {
props: {
foo: await fetch("api/foo") as number // changed my mind, now it's a number
}
}
}
</script>
<script lang="ts">
export let foo: string // ouch, forgot to change the type here
</script>
Describe the proposed solution
I won’t describe a full proposed solution as I don’t know about Svelte and SvelteKit internals, but instead I will share raw ideas that need refinement and further thinking.
With Vue and the script setup
syntax, it is actually possible to use await
in the main script section.
This wonderful feature brings that kind of syntax:
<script lang="ts">
const foo = await fetch("api/foo") as number
</script>
Which is extremely concise and type-safe.
I know for sure this is not a small feature, it would need changes even in the Svelte internal parts, not just SvelteKit.
Just making await
working will not be a magical solution ; there should be a way to prevent data-fetching first from server and then from client after hydration. To achieve this in a clean way, Nuxt 3 uses the useFetch
composable:
<script setup lang="ts">
const foo = useFetch("api/foo")
</script>
Which is not exactly the same approach as the await
but quite close. Maybe SvelteKit should define its own useFetch
/ useServerLoader
- or whatever it is named - utility function to deal with asynchronous server-side loading?
To deal with page parameters and similar stuff, I would use the same syntax as in client-side, ie with $page
store. That simplifies things as you share one syntax for server and client.
I think this feature should be thought taking in consideration other issues about SvelteKit loading, as it might resolve them in an elegant way as well:
- A function for getting the current load arguments #2979
- RFC: svelte-urql: enable to query in SvelteKit load() function #1819
- Multiple load functions #3042
Alternatives considered
No response
Importance
would make my life easier
Additional Information
No response
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:6 (3 by maintainers)
I’d love to see top-level await in Svelte, i think it would be a very elegant api to allow a streaming api and a great alternative to React’s suspend.
it also unlocks using
{#await}
blocks on the server.But although the usage is simple, the impact on the svelte codebase is non-trivial:
It moves the responsibilities of async behavior outside of the component:
It complicates things because mounting, #if, #each & #key are no longer synchronous.
Hydration is also affected, you don’t want to see the server rendered page and go into a pending state on the client and reload everything again. So all top-level awaits should be preresolved in the hydration step and use the values from the server. I think this is doable.
if hydratingPhase then return serialized result else evaluate the await expression
It should reject the promise if the resolved value could not be serialized.Speaking of rejected promises, i’d suggest to only serialize the successful promises:
That a lot of work, but it definitely on my wishlist for Svelte, just imagine how much it will improve working with promises.
Top-level await does not need newer versions of node since the code inside ‘<script>’ is put inside a function.