Skip to content

Out of order HTML streaming ("patching") #11542

@noamr

Description

@noamr

What problem are you trying to solve?

When delivering HTML documents, often parts of the document are ready in the server before others. This order might not be the same as the DOM order.

For example, a <select> might appear early in the DOM, with the <option> elements for it fetched from a database in parallel, or 3rd party widgets that have a placeholder that gets filled later.

What solutions exist today?

Some frameworks like React & Svelte offer support for out-of-order streaming. React does so by injecting inline <script> elements that modify the DOM.

How would you solve it?

Suggesting to have a contentname attribute, and extending the <template> element to make it into a patch:

  • <template contentfor="foo"> (a patch) would target an element with the given contentname=foo (an outlet).
  • The first patch replaces the outlet's children, the next ones append (by default).

Additional/supporting APIs

  • The contentmethod attribute can change the default behavior, with values equivalent to DOM methods (replace-with, replace-children, after, before, append, prepend).
  • A contentrevision attribute can be present on both the patch and the outlet. If they are identical, the patching is skipped.
  • The outlet lookup is done with the template's parent as the scope, so those patches can't be deeply nested and then target some external element. An exception is that patches that are direct descendants of <body> can target the whole document.
  • In addition, the lookup is tree-scoped and has no affordances for escaping the shadow root.
  • Patching into a fragment works, but the same rule applies - which means that the patch cannot escape the fragment.
  • The target would receive pseudo-classes that reflect its patching status (:updating and :pending?)
  • We should consider a JS getter that reflects the current state of patching.

Gritty details:

  • Scripts that are part of a patch are executed as normal if performed as part of the main parser.
  • Scripts, styles, other RAWTEXT elements (e.g. xmp), <plaintext> and the document (HTML) element cannot be directly streamed into. RCDATA elements (title/textarea) can.
  • From a parser perspective, a setup similar to fragment parsing is set up inside the <template>'s stack of open items. This makes parsing behave like parsing into the context element and into the <template> at the same time. The insertion target is the context element.
  • Conflicts/mismatches are handled as per A way to stream content into an element #2142 (comment)

Alternative: using <script> with escaping

Using <template> feels natural as a way to stream HTML. However, it has the constraint of not being able to stream directly into scripts and styles (RAWTEXT).

An alternative would be to have the patch be escaped HTML inside a <script> element. Then we can use the existing tokenizer state of the script element, unescape the text, and pass it to a second parser that inserts the elements to the correct context.

This however feels unnatural as HTML passed over HTTP should not require escaping :)
Since we can add a replaceWith mode in the future (instead of the current replaceChildren mode), it might be a better option than requiring the HTML to be escaped.

Potential future enhancements

  • Fetching and patching from an external resource (using the existing src attribute))

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions