Skip to content
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

<:Async> component #742

Closed
Rich-Harris opened this issue Aug 1, 2017 · 19 comments
Closed

<:Async> component #742

Rich-Harris opened this issue Aug 1, 2017 · 19 comments

Comments

@Rich-Harris
Copy link
Member

A proposal for first-class async component support:

<:Async src:'./Widget.html' loading:LoadingComponent error:ErrorComponent foo='{{bar}}'>
  contents go here
</:Async>

<script>
  import LoadingComponent from './LoadingComponent.html';
  import ErrorComponent from './ErrorComponent.html';

  export default {
    components: {
      LoadingComponent,
      ErrorComponent
    }
  };
</script>

Behind the scenes that would become roughly equivalent to this...

{{#if ready}}
  <Widget foo='{{bar}}'>
    contents go here
  </Widget>
{{elseif error}}
  <ErrorComponent/>
{{else}}
  <LoadingComponent/>
{{/if}}

...except that you wouldn't need to declare Widget ahead of time.

It would get transpiled to import('./Widget.html').then(...) in dom mode, but could be an inline require('./Widget.html') in ssr mode.

Advantages of this approach over a 'just use a component' approach, for the benefit of @TehShrike 😉 :

  • One clear blessed way to do it, with documentation etc, that's used consistently across the ecosystem. No need to mess around with external libraries. It's the difference between <ReactCSSTransitionGroup> and in:fade.
  • Optimisations. We haven't talked much yet about app-level analysis/compilation, but there are certain potential compile-time wins that you get from knowing the structure of an application ahead of time. The more you know...
  • With dynamic components you have a tricky SSR problem — a hypothetical <Async> component (as opposed to <:Async>) would have to do its work in oncreate, which doesn't run on the server. With <:Async> we can load the component synchronously with require(...) and generate markup without 'loading...' messages.
  • It's not mutually exclusive with 'just use a component' approaches if you have some truly exotic requirements.
@psirenny
Copy link

Is there a work around until the async component is created?

@PaulBGD
Copy link
Member

PaulBGD commented Oct 16, 2017

I feel like this could easily be implemented in userland if we provided a way to pass components as properties.. (#640)

@psirenny
Copy link

Yea, I totally agree and component passing would provide lots of other benefits as well.

@constgen
Copy link

Just an idea:

<Widget loading:LoadingComponent error:ErrorComponent foo='{{bar}}'>
  contents go here
</Widget>

<script>
  import LoadingComponent from './LoadingComponent.html';
  import ErrorComponent from './ErrorComponent.html';

  const Widget = import('./Widget.html');

  export default {
    components: {
      LoadingComponent,
      ErrorComponent,
      Widget
    }
  };
</script>

Every component during compilation should be checked if it is an instance of Promise and processed in a different way. Will it work?

@aubergene
Copy link

I think this is now effectly handled with #952

@Rich-Harris
Copy link
Member Author

Not quite — #952 addresses the problem of async data, but not async components, i.e. you can't lazily load the <ChatToACustomerServiceRepresentative> component here:

<button on:click='set({ chatting: true })'>
  chat to a customer service representative
</button>

{{#if chatting}}
  <ChatToACustomerServiceRepresentative/>
{{/if}}

It will be possible to do that with dynamic components though, which are almost ready (#971):

{{#if chatting}}
  <LazyLoad src='/components/ChatToACustomerServiceRepresentative.html'>
    <div>loading...</div>
  </LazyLoad>
{{/if}}
<!-- LazyLoad.html -->
{{#await promise}}
  <slot></slot>
{{then constructor}}
  <:Component {constructor}/>
{{catch error}}
  <p>oops! please try reloading the page</p>
{{/await}}

<script>
  export default {
    computed {
      promise: src => import(src)
    }
  };
</script>

(I'm glossing over some details, of course — not least of which is the fact that there's no way to pass arbitrary props down via LazyLoad to :Component, or events back up. But we'll cross that bridge eventually.)

@aubergene
Copy link

aubergene commented Dec 4, 2017

Ah, yes sorry it's because so far all my code has been bundled in a single app so I was thining is makes barely makes any difference once the code is on the page. So the idea is you'd compile out separate components and then their source is dynamically fetched and loaded? Wouldn't that lead to calls for code splitting too?

Edit: yes this will be amazing 👍

@ceremcem
Copy link

ceremcem commented Jul 25, 2018

In Ractive, I'm following this approach:

  1. Create a simple component.
  2. Use it anywhere you like in your app.
  3. When you need to remove it from your main bundle and load asynchronously:
    1. Add ASYNC postfix to your original component name

       Ractive.components.fooASYNC = Ractive.extend(...)
      
    2. Create a synchronizer proxy with your original component's name

       Ractive.partials.foo = getSynchronizer();
      
    3. Remove fooASYNC (and its dependencies) from your bundle and load it any time in the future with any method you like (XHR, websockets, etc...)

    4. Send a signal to the synchronizer when your component is ready.

Demo

See https://github.com/ceremcem/ractive-synchronizer

Advantages

  1. Convert any sync component into async component with only 2 lines of modification.
  2. Ability to decide when, if and how you would load the component.

@Snugug
Copy link

Snugug commented May 12, 2019

Been trying to hack together asynchronous routing w/o Sapper and this would help greatly

@brucou
Copy link

brucou commented Jun 23, 2019

Hi guys,

I am fairly new to Svelte and already trying to do complicated stuff with it, so I hope you will bear with me. I am working on a generalization of the problem described in this GitHub issue based on state machines. My idea is an API like:

<Async on:event={handleEvent}  startWith="loading">
  <LoadingComponent slot="loading" task={asyncLoadWidget} />
  <ErrorComponent slot="error" />
  <Widget slot="loaded" />
<Async />

The LoadingComponent is concerned with performing some asynchronous actions AND sending an event (say, READY event or ERROR event) when done.

The Widget component may be asynchronously loaded as in this issue or an already available component all the same.

The Async component receives events and update its state accordingly, and displays the slots corresponding to the state it is in.

Needless to say, the approach is pretty generic:

  • it could work to handle features similar to React Suspense without doing anything fancy (instead of throwing a promise like Suspense does, just dispatch an event to the controlling machine).
  • it generalizes dynamic components <svelte:component> (which are basically state machines with only one state)

My question is, given that component events do not bubble, if LoadingComponent dispatches a READY event, will the Async component receive it? If not, how to handle it in idiomatic Svelte? I am a bit confused with the scoping issues and what is a parent and a child. I understand that <Async> is the parent of <LoadingComponent> so I understand it should receive the event??

On another note, what do you think at first sight about the proposed API?

@brucou
Copy link

brucou commented Jun 28, 2019

I finalized a proof of concept for Suspense. This example was useful and outlined some benefits and some difficulty of Svelte's compiler approach. I will address this in a new issue. I assume the async component loading would fit in the same frame.

@shavyg2
Copy link

shavyg2 commented Jul 1, 2019

is the <:Component/> available in v3?

@mrkishi
Copy link
Member

mrkishi commented Jul 1, 2019

@shavyg2 <:Component> is now <svelte:component>. We don't have a builtin <:Async> but that can be implemented on <svelte:component> with #await and dynamic import().

Tutorial and docs.

@northkode
Copy link

@mrkishi how does one dynamic import a component in?
The example show direct importing.. do we have to write custom logic to detect what component to load and then do an await on a promise that just waits around for the logic to know what to load?

@antony
Copy link
Member

antony commented Feb 1, 2020

Question - are we happy with the flexibility that Svelte 3 provides with {#async} and <svelte:component> or is there a burning desire for what would be something like <svelte:component async src="./Component.svelte>?


@TehShrike
Copy link
Member

TehShrike commented Feb 2, 2020

ah yeah, per Rich's comment, {#async} and <svelte:component> together seem to completely solve this issue.

@frederikhors
Copy link

ah yeah, per Rich's comment, {#async} and <svelte:component> together seem to completely solve this issue.

Can you post an example please?

@pngwn
Copy link
Member

pngwn commented Feb 2, 2020

Something like this REPL should do it.

@Conduitry
Copy link
Member

As I write this, the poll above barely favors having a new syntax, 21 to 18, but I still don't think this is something we want to do. An async component is something that's trivial to do with the tools that already exist, and without having to implement or maintain or document anything new in the compiler. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests