html
html copied to clipboard
<teleport> element
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:
- 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).
- 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.
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>
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.
Thank you @annevk! This makes sense, sorry for not paying attention to the FAQ first, I've tried to address your concern.
@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.
@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?
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.
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.
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.
Why would you need to style inside the shadowroot? The shadowroot just contains two slots so everything is in light DOM.
@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.
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 https://github.com/whatwg/html/issues/10273 talks about slotting indirect children and so might be an option to explore here also.
@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.