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

<teleport> element #9614

Open
CyberAP opened this issue Aug 18, 2023 · 12 comments
Open

<teleport> element #9614

CyberAP opened this issue Aug 18, 2023 · 12 comments
Labels
addition/proposal New features or enhancements needs concrete proposal Moving the issue forward requires someone to figure out a detailed plan

Comments

@CyberAP
Copy link

CyberAP commented Aug 18, 2023

It would be nice for HTML to provide a native <teleport> element that would render its contents at the target destination instead of in-place. This mostly applies to when the document is still streaming.

Motivation

Right now there's no mechanism to redirect the underlying document stream to another part of the document. This means you can only write to the end of the document. This is quite limiting because sometimes a part of the document can be computationally expensive to render on the server or could take long to serve to the client. In order to solve that we can show a temproary placeholder in place and replace it later as soon as we get the HTML. There are solutions that can solve this problem already but they have their limitations:

  1. Buffer the response (i.e. wait for element to fully be streamed from the server) and use JavaScript to manually replace placeholder with the streamed contents. The main problem here is that the response always has to be buffered, that means the client will not see any of the contents unless everything is fully fetched. This is a severe limitation because it leaves us without progressive rendering that HTML can offer and the user experience is limited by their internet connection (the slower your network the later you'll see any content).
  2. Attach a new document stream. This solves the problem more effectively at the cost of running another HTTP request to the server. This is also limiting because it might take longer to get a new response from server than stream it right away with the first document stream. This solution involves JavaScript as well. Another downside here is that we can't execute JavaScript with that solution (<script> rendered within a new document stream does not execute).

With a <teleport> element we shouldn't have any of the downsides listed above and it would also siginficantly simplify current solutions to the problem that involve complex code on either a client or a server. The introduction of this element should also open up a new class of streaming-first solutions to web apps.

@keithamus
Copy link
Contributor

Sounds a bit like slots:

<body>
  <tele-port>
    <template shadowrootmode="open">
      <slot name="replace"></slot>
      <slot></slot>
    </template>
    <div class="body">
      <h1>The rest of my web page goes here</h1>
    </div>

    <!-- a bunch of async ops -->

    <div slot="replace">new content gets streamed out-of-order!</div>

  </tele-port>
</body>

@annevk
Copy link
Member

annevk commented Aug 18, 2023

Hey @CyberAP, I'd recommend focusing on 1 and 2 of https://whatwg.org/faq#adding-new-features for now and to try and flush those out. A new element needs quite a bit of justification and ideally there's ample precedent for it in userland.

@CyberAP
Copy link
Author

CyberAP commented Aug 26, 2023

Thank you @annevk! This makes sense, sorry for not paying attention to the FAQ first, I've tried to address your concern.

@CyberAP
Copy link
Author

CyberAP commented Aug 26, 2023

@keithamus good point! The problem with this solution is that you basically have to wrap your whole app within a Web Component and you also have to know where to place the slots in advance, which is not possible in some cases. I did not manage to test whether declarative shadow root contents get replaced without buffering though.

Edit: it works! Though the limitations mentioned above still apply.

@0xdevwrite
Copy link

@CyberAP Would using iframes help solve this problem for you? If async rendering is enough of a concern for you application to solve for (due to computationally expensive parts) then perhaps this hinting that those particular pieces should be considered separate resources with their own URL on your server.

For example:

<body>
  <div>start of content here</div>
  <iframe src="computationally-expensive"></iframe>
  <div>rest of content</div>
</body>

Are there constraints you see this imposing that wouldn't work for you case?

@brandonmcconnell
Copy link

brandonmcconnell commented Oct 2, 2023

thoughts re iframe workaround

I don't think iframes would be a fix for this. There are many cases where the iframe content would need to be sized dynamically, which then requires using window.parent.postMessage for the frame to declare its own minimum height given a width so the parent and child frames can display the frame as a seamless piece of the UI.

This same limitation also plays into any bit of data that both frames need to share with each other.

thoughts re slot workaround

And for the same limitations that @CyberAP mentioned, I don't personally like the "slot" idea either if it would require wrapping the entire app in a web component, thereby suppressing the entire app into the ShadowDOM, which comes with a number of limitations on its own, and many frameworks have difficulty working within the ShadowDOM without the use of a tool like react-shadow-scope.

general supporting argument and field research (re existing JS-based solutions)

This <teleport> element is a very common and necessary piece of most if not all JS frameworks (typically referred to as "portals"), and having something like this in native HTML would be a huge win.

@keithamus
Copy link
Contributor

basically have to wrap your whole app within a Web Component

You could use a <div> instead of <tele-port>.

suppressing the entire app into the ShadowDOM, which comes with a number of limitations on its own

Could you describe some of these?

and many frameworks have difficulty working within the ShadowDOM without the use of a tool like react-shadow-scope.

As far as I understand it any new element would have similar issues with pre-existing tooling and libraries. I’m struggling to see how creating a new element would not suffer from the same issues.

I’m not advocating for using ShadowDOM for this case, but I am curious what the specific barriers are; it would help establish motivation/justification for a specific solution to this use case, as well as help establish some criteria/constraints.

@keithamus keithamus added addition/proposal New features or enhancements needs concrete proposal Moving the issue forward requires someone to figure out a detailed plan labels Nov 4, 2023
@CyberAP
Copy link
Author

CyberAP commented Aug 23, 2024

Could you describe some of these?

@keithamus the main issue is styling. You have to put your primary content into a <template> element in order to declare slots deep inside the DOM tree. This creates shadow root for your content, which means you have to put your styles within the <template> tag or adopt them with JS. But this won't work for the slotted content because it goes outside of the <template> element. So you'll have to separate (or duplicate) the styles for your primary and slotted content. Also, if you load styles asynchronously you'll have to repeat that same step in JS (attach to both <head> and adopt stylesheets). Technically, you can make it work (for example with MutationObserver, so you won't have to deal with it implicitly), but it's quite complex.

Also, you can not stream content before you close the <template> tag. This is similar to the 'streaming' (although it's not streaming per se) approach some of the frameworks take. With <teleport> you should be able to do so.

@keithamus
Copy link
Contributor

Why would you need to style inside the shadowroot? The shadowroot just contains two slots so everything is in light DOM.

@CyberAP
Copy link
Author

CyberAP commented Aug 23, 2024

@keithamus how you define deep slots outside of shadow root? This won't work:

<tele-port>
  <template shadowrootmode="open">
    <slot><slot>
  </template>

  <div class="body">
     <slot name="replace"></slot>
     <h1>The rest of my web page goes here</h1>
  </div>

  <!-- a bunch of async ops -->

  <div slot="replace"><div class="foo">new content gets streamed out-of-order!</div>
</tele-port>

You have place your primary content inside the shadow root in order to do this.

@keithamus
Copy link
Contributor

keithamus commented Aug 23, 2024

Why can't you move the shadowroot to the div class=body?

<div class="body">
  <template shadowrootmode="open">
     <slot name="replace"></slot>
    <slot><slot>
  </template>
  <h1>The rest of my web page goes here</h1>
  <!-- a bunch of async ops -->

  <div slot="replace"><div class="foo">new content gets streamed out-of-order!</div>
</div>

Issue #10273 talks about slotting indirect children and so might be an option to explore here also.

@CyberAP
Copy link
Author

CyberAP commented Aug 23, 2024

@keithamus this only works if you place the slotted content within the wrapper element, which defeats the purpose. This now becomes a blocking operation: we can't continue streaming the rest before we resolve the slots.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs concrete proposal Moving the issue forward requires someone to figure out a detailed plan
Development

No branches or pull requests

6 participants
@keithamus @CyberAP @annevk @brandonmcconnell @0xdevwrite and others