svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Top-level await

Open joas8211 opened this issue 5 years ago • 47 comments

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?

joas8211 avatar Oct 05 '20 19:10 joas8211

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 avatar Oct 15 '20 11:10 antony

@antony What do you mean? Acorn already supports "top-level await" with allowAwaitOutsideFunction: true. And I'm not trying to have a real top-level await (top-level of module). I'm just trying to have await for component's script-tag (init script) that is actually wrapped in a function that can be changed to an async function just like I did in my fork.

joas8211 avatar Oct 15 '20 18:10 joas8211

@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 await statement in the necessary components.

probablykasper avatar Apr 12 '21 14:04 probablykasper

I opened this on kit. Same question: https://github.com/sveltejs/kit/issues/941.

How to use acorn options?

frederikhors avatar Apr 12 '21 15:04 frederikhors

@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 avatar Apr 12 '21 19:04 joas8211

@joas8211 Sorry for my noobness, could it be possible to add syntax for async instead of a compiler option? Like this:

<script async>

</script>

98mux avatar Apr 12 '21 22:04 98mux

@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 const instance = await Component.init(options); and synchronous aka. "normal" component is initialized with the class constructor const instance = new Component(options);.

2: Synchronous components cannot contain asynchronous components since they must function synchronously.

joas8211 avatar Apr 12 '21 23:04 joas8211

@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 avatar Apr 13 '21 00:04 probablykasper

@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 avatar Apr 13 '21 05:04 joas8211

@joas8211 Is the only issue that SSR wouldn't support it? Does SSR even support loading dynamic components currently?

probablykasper avatar Apr 13 '21 23:04 probablykasper

@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 avatar Apr 14 '21 04:04 joas8211

@joas8211 In that case I think it might be fair to consider special SSR handling of await as a separate issue, and then SSR could handle await like a normal promise for now. Besides, what would SSR do if the response you get in your fetch depends on a cookie, user agent or something like that?

probablykasper avatar Apr 14 '21 04:04 probablykasper

@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}

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.

tonprince avatar Jun 06 '21 08:06 tonprince

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 avatar Jul 11 '21 08:07 joas8211

@joas8211 You might want to checkout solidjs if you like jsx. Don't know if it has async tho

98mux avatar Jul 11 '21 09:07 98mux

@filipot Thank you for suggestion, but it seems Solid doesn't support asynchronous components / rendering.

joas8211 avatar Jul 11 '21 09:07 joas8211

asynchronous components / rendering.

What do you EXACTLY mean, @joas8211?

frederikhors avatar Jul 11 '21 09:07 frederikhors

@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.

joas8211 avatar Jul 11 '21 09:07 joas8211

Yeah, I mean this can be achieved with Svelte, can you write an example?

I opened both:

  1. https://github.com/sveltejs/svelte/issues/5017 and
  2. https://github.com/sveltejs/svelte/issues/2979

frederikhors avatar Jul 11 '21 10:07 frederikhors

@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: https://github.com/sveltejs/svelte/issues/5501#issuecomment-818447546

joas8211 avatar Jul 11 '21 10:07 joas8211

@joas8211 sapper and svelte-kit allows you to load data before you render the page https://kit.svelte.dev/docs#loading

98mux avatar Jul 15 '21 13:07 98mux

@filipot Thank you for suggestion, but it seems Solid doesn't support asynchronous components / rendering.

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.

ryansolid avatar Sep 09 '21 07:09 ryansolid

This feature would be incredibly useful to enable the use of wasm by svelte modules, since wasm init functions are async. See proof of concept using sveltekit pages: https://github.com/cfac45/sveltekit-rust-ssr-template

doomnoodles avatar Oct 06 '21 00:10 doomnoodles

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}

kryptus36 avatar Jun 12 '22 00:06 kryptus36

It has been 2 years, React is implementing this now because they realized web is async

Basically it should be like: Component <script> runs async and awaited before render, and being awaited all the way up to the Root that way we can do async tasks such as fetching on SSR. for example we can fetch data from api and render result on SSR. and if we want to fetch stuff, do async stuff on CSR we can use onMount() for that

DeepDoge avatar Oct 18 '22 09:10 DeepDoge

It has been 2 years, React is implementing this now because they realized web is async

Basically it should be like: Component <script> runs async and awaited before render, and being awaited all the way up to the Root that way we can do async tasks such as fetching on SSR. for example we can fetch data from api and render result on SSR. and if we want to fetch stuff, do async stuff on CSR we can use onMount() for that

Vue has it too

bugproof avatar Oct 18 '22 17:10 bugproof

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.

kryptus36 avatar Oct 18 '22 18:10 kryptus36

Vue 3 supports top level await. When Svelte gets this mega convenient feature?

bomzj avatar Dec 25 '22 23:12 bomzj

Just saying that I think this will improve DX quite a lot.

axerivant avatar Jan 15 '23 02:01 axerivant

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.

joas8211 avatar Jan 15 '23 10:01 joas8211