You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: patching-explainer.md
+98-16Lines changed: 98 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,7 @@
1
1
# Interleaved HTML streaming (patching)
2
2
3
3
## Motivation
4
+
4
5
Streaming of HTML existed from the early days of the web, serving an important purpose for perceived performance when loading long articles etc.
5
6
However, it always had the following major constraints:
6
7
1. HTML content is streamed in DOM order.
@@ -15,41 +16,93 @@ This proposal introduces partial out-of-order HTML streaming as part of the web
15
16
16
17
## Declarative patching
17
18
18
-
Patches are delivered using a `<template>` element with the `contentmethod` attribute and target an existing elements in the DOM with the `contentname` attributes. These patches require no scripts to apply (are declarative) and can appear in the main response HTML to support out-of-order streaming.
19
+
Patches are delivered using a `<template>` element with the `for` attribute and target an existing elements in the DOM with the `sink` attribute. These patches require no scripts to apply (are declarative) and can appear in the main response HTML to support out-of-order streaming.
19
20
20
21
Patches can be be applied later in the page lifecycle using JavaScript, see [script-initiated patching](#script-initiated-patching).
21
22
22
23
### Proposed markup
23
24
24
-
The `contentname` attribute is used to identify an element which can be patched:
25
+
The `sink` attribute is used to identify an element which can be patched:
25
26
26
27
```html
27
-
<sectioncontentname=gallery>Loading...</section>
28
+
<sectionsink="gallery">
29
+
Loading...
30
+
</section>
28
31
```
29
32
30
33
The content is then patches using a `<template>` element:
The element name (`section`) needs to be repeated so that children are parsed correctly, but only the child nodes are actually replaced in this example.
41
+
The existing children of the `<section>` element ("Loading...") are removed and the final DOM will be:
39
42
40
-
There are two proposed `contentmethod` values:
43
+
```html
44
+
<sectionsink="gallery">
45
+
Actual gallery content
46
+
</section>
47
+
```
41
48
42
-
-`append` inserts nodes at the end of the element, similar to `element.append(nodes)`.
43
-
-`replace-children` replaces any existing child nodes, similar to `element.replaceChildren(nodes)`.
49
+
A new `<!marker>` node is introduced to give more granular control over which nodes are replaced. With a single marker, the marker node is replaced.
44
50
45
-
At a low level, the only difference is that is `replace-children` removes existing nodes and then appends new nodes, while `append` only appends new nodes.
51
+
```html
52
+
<ulsink="list">
53
+
<li>first item</li>
54
+
<!marker>
55
+
<li>last item</li>
56
+
</ul>
57
+
58
+
<templatefor="list">
59
+
<li>middle item</li>
60
+
</template>
61
+
```
46
62
47
-
A few details about interleaved patching:
48
-
- Templates with a valid `contentmethod` are not attached to the DOM.
49
-
- If the patching element is not a direct child of `<body>`, the outlet has to have a common ancestor with the patching element's parent.
50
-
- The patch template has to be in the same tree (shadow) scope as the outlet.
63
+
To replace a range of nodes, `start` and `end` attributes are used:
51
64
52
-
See the https://github.com/whatwg/html/pull/11818 for the full processing model and details.
65
+
```html
66
+
<sectionsink="section">
67
+
<header>header stuff</header>
68
+
<!marker start>
69
+
Placeholder content
70
+
<!marker end>
71
+
<footer>footer stuff</footer>
72
+
</section>
73
+
74
+
<templatefor="section">
75
+
<p>Real content</p>
76
+
</template>
77
+
```
78
+
79
+
Finally, to support multiple ranges, marker nodes can have a `name` attribute. The names must match one of the tokens in the `sink` attribute, and any number of ranges can be exposed:
80
+
81
+
```html
82
+
<divsink="part-one part-two">
83
+
<!marker start name="part-one">
84
+
Placeholder content
85
+
<!marker end name="part-one"
86
+
<hr>
87
+
<!marker start name="part-two">
88
+
Placeholder content
89
+
<!marker start name="part-two">
90
+
</div>
91
+
92
+
<templatefor="part-one">
93
+
<p>Actual 1st part of the content</p>
94
+
</template>
95
+
96
+
<templatefor="part-two">
97
+
<p>Actual 2nd part of the content</p>
98
+
</template>
99
+
```
100
+
101
+
A few details about patching:
102
+
103
+
- Templates with a valid `for` attribute are not attached to the DOM, while templates that don't apply are attached to signal an error.
104
+
- If the patching element is not a direct child of `<body>`, the sink has to have a common ancestor with the patching element's parent.
105
+
- The patch template has to be in the same tree (shadow) scope as the sink.
53
106
54
107
### Interleaved patching
55
108
@@ -283,7 +336,7 @@ Possible ergonomic additions this option:
283
336
### Avoiding overwriting with identical content
284
337
285
338
Some content might need to remain unchanged in certain conditions. For example, displaying a chat widget in all pages but the home, but not reloading it between pages.
286
-
For this, both the outlet and the patch can have a `contentrevision` attribute. If those match, the content is not applied.
339
+
For this, both the sink and the patch can have a `contentrevision` attribute. If those match, the content is not applied.
287
340
288
341
### Streaming to non-element ranges
289
342
@@ -329,6 +382,35 @@ This can be done with a `patchsrc` attribute.
329
382
330
383
Enabling remote fetching of patch content would act as a script in terms of CSP, with a CORS-only request, and would be sanitized with the same HTML/trusted-types restrictions as patching using script.
331
384
385
+
## Alternatives considered
386
+
387
+
### `contentmethod` attribute
388
+
389
+
An earlier proposal that did not have marker nodes used a `contentmethod` attribute to control which nodes are removed and where new nodes are inserted. The `contentname` attribute was used on both `<template>` and the target element to link them.
-`append` inserts nodes at the end of the element, similar to `element.append(nodes)`.
404
+
-`prepend` inserts nodes at the end of the element, similar to `element.prepend(nodes)`.
405
+
-`replace-children` replaces any existing child nodes, similar to `element.replaceChildren(nodes)`.
406
+
-`replace` replaces the element itself, similar to `element.replaceWith(nodes)`.
407
+
408
+
Weaknesses of this design are:
409
+
410
+
- Doesn't support replacing arbitrary ranges of nodes, only an element or all of its children.
411
+
- In order to support patching `<title>`, which uses the [RCDATA tokenizer state](https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state), the tag name of the target element must be repeated. TODO
412
+
-`prepend` can fail if the original first child of the element is removed, meaning that a patch can fail mid-stream, requiring some error handling/reporting.
413
+
332
414
## [Self-Review Questionnaire: Security and Privacy](https://w3c.github.io/security-questionnaire/)
0 commit comments