-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Top-level await #5501
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
Comments
Top level await I believe is something which would be provided by acorn, not something we would try to implement ahead of it. Once acorn supports top level await then we will gain support for it. |
@antony What do you mean? Acorn already supports "top-level await" with |
@antony Any word on what Jesse said? My use case: I have a module that loads some data asynchronously, which is then used by a bunch of components. All those components are shown to the user only after the data is loaded. If I make the loading async, it requires me to make a lot of changes to make it work, whereas a top-level await would mean I just put a single |
I opened this on kit. Same question: sveltejs/kit#941. How to use acorn options? |
@frederikhors I think you cannot change how the Svelte compiler parses JavaScript without modifying the compiler. And the Acorn flag alone wouldn't do you any good. The component script must be wrapped inside an async function in the final output for await to work. And that function must be run asynchronously (eg. with async-await) etc... So I might do the compiler option. But don't expect it too soon. It is a lot of work to do it properly. |
@joas8211 Sorry for my noobness, could it be possible to add syntax for async instead of a compiler option?
|
@filipot Probably not possible. Problem is with 1: the difference of how the components are initialized and with 2: mixing the different types of components. 1: Asynchronous component is initialized with asynchronous static function on components class 2: Synchronous components cannot contain asynchronous components since they must function synchronously. |
@joas8211 Does the initialization of an async component have to be async? Would something like this be viable: <script>
data = await import('module.js')
export let name = ''
function handler() {
data.name = 'updated'
}
</script>
<p on:click={handler>{name}</p> transforms to <script>
data = await import('module.js')
export let name = ''
async function handler() {
data = await data
data.name = 'updated'
}
</script>
{#await}
{:then data}
<p on:click={handler}>{data.name}</p>
{/await} |
@probablykasper For my use case, yes. I wanted to load dynamic components defined by component's prop or eg. external resource before rendering on server-side / build-time (SSR). SSR only runs the initial script (top-level of script tag) and does not wait for await keyword in the template. Here's some code I just made up to demonstrate my use case: <!-- ContentArea.svelte -->
<script>
export let id = 'main';
const response = await fetch(`/areas/${id}`);
const blocks = await response.json();
for (const block of blocks) {
block.component = (await import(block.module)).default;
}
</script>
{#each block as blocks}
<svelte:component this={block.component} {...block.props} />
{/each} |
@joas8211 Is the only issue that SSR wouldn't support it? Does SSR even support loading dynamic components currently? |
@probablykasper Well, it does support dynamic components with static import, but not with dynamic import aka. code splitting if you use Rollup. Because dynamic import is asynchronous. |
@joas8211 In that case I think it might be fair to consider special SSR handling of |
I have the same issue and wanted to load dynamic components by an external list from sveltekit-load. In dev-mode everything is fine but when trying to load it in preview mode the components are not rendered. |
I gave up trying to make Svelte asynchronous and preserving all the existing features. I tried to make my own fork dropping all not supported features, but tests showed me how it's really hard to make stable. So I switch framework to Crank.js for my project. It's very different from Svelte, but it ticks all my requirements except reactivity which I can solve. |
@joas8211 You might want to checkout solidjs if you like jsx. Don't know if it has async tho |
@filipot Thank you for suggestion, but it seems Solid doesn't support asynchronous components / rendering. |
What do you EXACTLY mean, @joas8211? |
@frederikhors I mean an ability to halt rendering for the duration of an asynchronous task. Like for example loading data or subcomponents. Suspense type of solutions won't cut it for server-side rendering. |
Yeah, I mean this can be achieved with Svelte, can you write an example? I opened both: |
@frederikhors It seems your feature request #5017 has not been acted on. If it would be implemented then I would not have this issue. I wrote an example of my use-case in a previous comment: #5501 (comment) |
@joas8211 sapper and svelte-kit allows you to load data before you render the page https://kit.svelte.dev/docs#loading |
To clarify Solid supports async. It is granular though. Halting at a component level makes no sense for Solid. Cranks approach while interesting is too blocking for our needs. Having components halt reduces our ability to parallelize work within a single component and potential unrelated sub trees. We use suspense on the server to do Async SSR and support out-of-order streaming where we send placeholders out synchronously and then load in the content as it completes over the stream. The way we accomplish this is through the Resource API which handles automatic serialization of data and basically completes a promise in the browser that started on the server. Suspense boundaries know how to handle this by design and in so you get a universal system that just works. |
This feature would be incredibly useful to enable the use of wasm by svelte modules, since wasm init functions are async. |
I am trying to do the same thing as @joas8211 : I have a json template that describes which components are rendered and their content. It works perfectly in CSR but in SSR there's seemingly no way to dynamically load components. {#await appImport(widgetPath) then component}
<svelte:component this={component.default} bind:data={data.data} />
{/await} |
It has been 2 years, React is implementing this now Basically it should be like: Component |
I thought of this and #958 (which is from 2017) when I saw the React announcement. It would be nice to know if this is on the radar of the dev team. Sveltekit doesn't fit my needs, and as such I may be faced with using something else. |
Vue 3 supports top level await. When Svelte gets this mega convenient feature? |
Just saying that I think this will improve DX quite a lot. |
If I remember correctly I used async-await through-out Svelte codebase to allow await at top-level of component script. That wouldn't be possibe without major API changes because currently the execution starts from component's constructor (https://svelte.dev/docs#run-time-client-side-component-api-creating-a-component). Constructors in JavaScript are always synchronous. So if there's so willingness for breaking changes (eg. new major version), then component top-level await cannot be achieved. |
You could do it with an explicit |
@akvadrako The problem is how the function execution is expected to return. I incorrectly linked the client-side component API, but the problem really is the server side API. Client-side execution continues after calling the constructor until |
Given that Rich himself posted #653 and superseded it with #958 he's aware of the utility of it. Given that react took the effort on (and it took them years to achieve) and Vue 3 included it, I think there's a general consensus that it's useful. Without it you need to use tools like Puppeteer to pre-render for SEO for some render flows, a practice that was once recommended by Google, but is now seen as less favorable. All competing frameworks now have a similar function, and it makes some ideas much more difficult (or impossible) to achieve when page structure is dynamic. It's not really a question of whether it should be done (you'd have a hard time arguing the contrary imo) but whether it's going to be made a priority. I am holding out hope, because to me this is the only thing Svelte doesn't do head and shoulders better than it's peers. The problem is this is a nearly non-optional feature for some projects. Without this, your SSR will stop at any call that requires a dynamic import, which seriously hamstrings the usefulness of svelte:component among other things. I'm hoping this gets put on the backlog for SvelteKit 2. It would be a big win. |
Coming from Chrome extensions development here. We are used to having top-level awaits by adding |
Still waiting for this feature, would be a great DX |
Update: Still waiting in 2024. |
As an addition to what i said earlier (#5501 (comment)) |
You mean still awaiting 🤣 (sorry couldn't resist). |
Svelte 5 and yet, no top level async/await |
Just to note here, each await call adds a very slightest delay because of polluting event loop. So if you don’t need async features of a library, import their sync version of a function instead. If you use async functions just like normal functions only with await keyword, then top-level await doesn’t make sense to you. In fact, this convenience only will make you create worse apps, where now in every single component you have tons of async functions that don’t actually use async feature. Of course there comes-in the sad reality, that libraries do not always provide synchronous versions of a function, then await comes-in handy again. await whatever() // ❌ bad async code, you don’t actually use async feature
whateverSync() // ✅ it’s better than previous example, same result but doesn’t pollute event loop Async makes sense only if you do something in-between: const data = whatever()
… // do something else, probably some UI stuff etc
await data // ✅ you seem to use async mindfully, congratz 🎉 Otherwise top-level await will make you a worse programmer when it comes (if it comes). Be mindful of this convenience. |
@jerrygreen That's not true, although you're right that async can make your app slower. The advantage of async is that it's non-blocking, allowing unrelated code in completely different parts of your app to run concurrently. That can make your app overall faster / more responsive if you have a long-running function. There's no difference between your own long-running async function and an "async feature" like |
What should a person do to get around this problem for the time being? |
I need this because I have some initialization code that must run before the framework loads |
Update: As I needed top-level await specifically for separate modules and not the code in
|
So the problem here is Svelte component's themselves can't be async, best you can do is using Since Svelte component's themselves can't be async, we have to use SvelteKit's PageLoad function, if we want page to not show up until everything it needs is loaded or downloaded. When ever you use a component that requires an async data, you also have to remember and load it on each-page that you use that component. for example with vanilla js, i can do something like this: async function renderPost(postId: string) {
const host = document.createElement("article")
const post = await fetchPost(postId)
const postProfile = renderPostProfile(post.profileId),
const postContent = renderPostContent(post.content)
host.append(
await postProfile,
renderRelativeDate(post.createdAt),
await postContent
)
return host
} In Svelte you can't make the SvelteKit can also use the same system to not change the page until the |
Node has top level await support now, wonder if we can revisit svelte's top level await. Also svelte will have to eventually cross this bridge and have async components like react... And with svelte components as functions now it could be easier to make them async down the line. |
I do think async components already are possible, however top-level await would be a great addition for svelte 5 ! |
2025.. being 4 year of this issue. or is there any trick we can achieve similar behavior. |
It's not an issue, it's a feature 😉 Adding top-level-await seems like it would be convenient for developers, but the implementations I could imagine are downgrades for users, it also adds a lot additional complexity and issues. The issues
Loading & error statesAs the component itself can't output anything yet, the responsibility of indicating a loading state or error state is moved to a parent component. <svelte:boundary>
{#snippet pending()}
<Spinner />
{/snippet}
<AsyncComponent />
<RegularComponent />
<SometimesAsync />
{#snippet failed(error, reset)}
<button onclick={reset}>oops! try again</button>
{/snippet}
</svelte:boundary> That looks like a reasonable solution, but it's not as simple as it looks. What if multiple blocks fail, should the What if an async component is displayed inside the What if Svelte instead used the pending snippet to replace each async component individually at their location in the DOM during the it's pending phase? All this will increase the amount of spinners and Cumulative Layout Shifts for users. Svelte intro & outro animationsWhen should intro animations start? Should intro animations wait until there are no pending components? When the Components are no longer needed and the outro animation starts, should that reject the promise? Unusable scenario'sMaking the setup phase of a component asynchronous splits the setup into multiple periods (it pauses at each await), but it still runs once. Therefor using a value from $props() to fetch data will be broken/buggy. This is because the result of the promise is only using the initial value. This makes it it less useful, as you need to handle the initial fetch and the updates separately. let data = $await(load(id)); That would allow re-executing the Tricks, workarounds & patternsI do understand the allure of top level await, especially when React seems to be able to do it with it's React Server Component and use hook. The {#await} block{#if browser}
{#await load(id)}
<Spinner />
{:then article}
<h1>{article.title}</h1>
{:catch error}
<p>oops!</p>
{/await}
{:else}
<Spinner />
{/if} The await block is very nice, if the A downside is that during server render the Another downside is that code inside the script section has no access to the result or state of the promise. Utility to use a promise as a signal<script>
const asyncState = createAsyncState(() => load(id));
const { data, error, pending } = $derived(asyncState);
</script>
<input type="number" bind:value={id} />
{#if pending}
<Spinner />
{:else if data}
<h1>{data.title}</h1>
{:else if error}
<p>oops!</p>
{/if}
See REPL for the implementation of This also only works client-side, but the callback passed to SvelteKit loadersUsing SvelteKit: Can the promise be determined by the url? Move the loading logic to the +page.js or +page.server.js Splitting componentsI like writing a component where all the data is always available, this makes the component reasoning about, testing and storybook easier. The logic & complexity of loading is not gone but moved to another component or +page.js file. AstroUsing another framework like Astro allows to use await inside the .astro components (which, just like RSC, don't support reactivity) and then use Svelte components where you need the reactivity, but I don't have much experience with this option (I like SvelteKit). |
I disagree with the issues you mention:
Errors and loading statesIt's already possible to throw errors in components today, and to load slowly (blocking). Async should ideally be treated the same way. Svelte intro & outro animationsIntro should not wait for other pending components. Unusable scenario - $props()I can't see any issue here. If you want something to re-run when |
The Svelte team has an experimental implementation of top-level await: #15845 This is client-side only (for now) and chose the following sensible behaviors:
|
This is continuation for #5351.
The problem is that there's no way to render an asynchronously loaded component on SSR. My use-case for asynchronously loaded components is rendering JAM-stack site's content from JSON files. My JSON-files describe page content as blocks, that are different components (type) and their props (data).
I want to have top-level await support for component's
<script>
that will allow awaiting for promises before component's initialization is finished.I've done an implementation for this, but changing initialization asynchronous is quite a breaking change. After the change it's not possible to initialize component with constructor, but instead we have to use a static async builder method
Component.init(options)
. Not being able to initialize synchronously breaks custom elements. Component updating also becomes asynchronous so assignments to props don't get reflected to DOM synchronously. That will also break a lot of code.If I make async initialization a compiler option, so that it doesn't trash backwards compatibility, would maintainers be willing to merge the changes? Is there any demand for this feature?
The text was updated successfully, but these errors were encountered: