@microsoft/webui-framework 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -38
- package/dist/decorators.js +9 -2
- package/dist/element/markers.d.ts +8 -0
- package/dist/element/markers.js +95 -16
- package/dist/element.d.ts +11 -2
- package/dist/element.js +87 -2
- package/dist/lazy-hydration-contract.d.ts +22 -0
- package/dist/lazy-hydration-contract.js +28 -0
- package/dist/lazy-hydration-coordinator.d.ts +2 -0
- package/dist/lazy-hydration-coordinator.js +380 -0
- package/dist/lazy-hydration-entry.d.ts +1 -0
- package/dist/lazy-hydration-entry.js +2 -0
- package/dist/lifecycle.d.ts +1 -0
- package/dist/lifecycle.js +29 -1
- package/dist/template-element.d.ts +37 -7
- package/dist/template-element.js +444 -132
- package/dist/template-types.d.ts +9 -8
- package/dist/template.d.ts +1 -1
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -108,6 +108,63 @@ lifecycle code, imperative methods, or state that TypeScript code reads or
|
|
|
108
108
|
mutates. `@observable` and `@attr` are optional; add them when JavaScript needs
|
|
109
109
|
to access the value or when the value is part of the component's public API.
|
|
110
110
|
|
|
111
|
+
### Offscreen work reduction
|
|
112
|
+
|
|
113
|
+
For components repeated beyond the initial viewport, put the complete policy on
|
|
114
|
+
the component template:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<template
|
|
118
|
+
w-render="lazy"
|
|
119
|
+
w-reserve-block-size="18rem"
|
|
120
|
+
>
|
|
121
|
+
<!-- Component content -->
|
|
122
|
+
</template>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`w-render="lazy"` combines visibility-deferred hydration with the browser's
|
|
126
|
+
`content-visibility: auto` rendering deferral. The reservation is the typical
|
|
127
|
+
rendered block size of one instance; WebUI emits it as
|
|
128
|
+
`contain-intrinsic-block-size: auto 18rem` before first layout. The SSR DOM
|
|
129
|
+
remains present, searchable, and accessible while the browser skips offscreen
|
|
130
|
+
style, layout, and paint work. The generated policy applies to instances in the
|
|
131
|
+
document, Light DOM, and standard Shadow DOM components. A `--dom=light`
|
|
132
|
+
component inside an authored shadow root needs the `style` CSS strategy or an
|
|
133
|
+
equivalent rule in that root's stylesheet because document styles cannot cross
|
|
134
|
+
the boundary.
|
|
135
|
+
|
|
136
|
+
Import the optional coordinator entry once before component modules:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import '@microsoft/webui-framework/lazy-hydration.js';
|
|
140
|
+
import './product-card.js';
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Use hydration-only deferral when rendering containment is not safe for a
|
|
144
|
+
component:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<template w-hydrate="lazy">
|
|
148
|
+
<!-- Component content -->
|
|
149
|
+
</template>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Components are eager by default. Instance attributes provide explicit escape
|
|
153
|
+
hatches:
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<!-- Keep rendering deferral, but hydrate this instance immediately. -->
|
|
157
|
+
<product-card w-hydrate="eager"></product-card>
|
|
158
|
+
|
|
159
|
+
<!-- Disable both rendering and hydration deferral for this instance. -->
|
|
160
|
+
<product-card w-render="eager"></product-card>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Client-created instances and reconnects after a successful mount remain eager.
|
|
164
|
+
If the optional entry or `IntersectionObserver` is unavailable, hydration falls
|
|
165
|
+
back to eager; `content-visibility` remains browser-managed. See
|
|
166
|
+
[Lazy Hydration](https://microsoft.github.io/webui/guide/concepts/hydration#lazy-hydration).
|
|
167
|
+
|
|
111
168
|
### Build with the WebUI plugin
|
|
112
169
|
|
|
113
170
|
```bash
|
|
@@ -184,17 +241,17 @@ development-only and is dead-code-eliminated from production bundles via the
|
|
|
184
241
|
Override the protected `hydratedCallback()` hook for work that requires the
|
|
185
242
|
component's bindings, events, and `w-ref` references to be ready. It runs
|
|
186
243
|
synchronously exactly once after the first successful ordinary SSR hydration,
|
|
187
|
-
client-created mount, deferred streamed activation, or dormant
|
|
188
|
-
Its once-latch is set before author code runs, so a thrown
|
|
189
|
-
retried on reconnect.
|
|
244
|
+
client-created mount, lazy activation, deferred streamed activation, or dormant
|
|
245
|
+
static-host wake. Its once-latch is set before author code runs, so a thrown
|
|
246
|
+
callback is not retried on reconnect.
|
|
190
247
|
|
|
191
248
|
`connectedCallback()` remains a native per-connection lifecycle. On ordinary
|
|
192
249
|
SSR and client-created mounts, `super.connectedCallback()` hydrates
|
|
193
|
-
synchronously, but a streamed `data-ws` root
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
250
|
+
synchronously, but a lazy root or streamed `data-ws` root can return while still
|
|
251
|
+
deferred. Therefore `connectedCallback()` cannot be used as a universal
|
|
252
|
+
post-hydration signal. Descendants must not structurally mutate a containing
|
|
253
|
+
component's SSR subtree before it hydrates, because hydration relies on stable
|
|
254
|
+
compiled paths.
|
|
198
255
|
|
|
199
256
|
### DOM strategy (`--dom`)
|
|
200
257
|
|
|
@@ -405,7 +462,7 @@ resource-constrained devices.
|
|
|
405
462
|
|
|
406
463
|
5. **Single-pass hydration via path mapping.**
|
|
407
464
|
SSR DOM is matched to compiled template bindings through
|
|
408
|
-
|
|
465
|
+
the lockstep hydration walk (`buildSSRIndex`). Ordinary buffered hydration
|
|
409
466
|
needs no marker comments or data attributes for binding resolution. The
|
|
410
467
|
hydration walk touches each DOM node exactly once.
|
|
411
468
|
|
|
@@ -469,7 +526,7 @@ Angular all require a JavaScript runtime on the server. This framework's SSR
|
|
|
469
526
|
is driven by data (template metadata + state values), not code. Any language
|
|
470
527
|
that can read the compiled metadata and produce HTML can serve as the SSR
|
|
471
528
|
backend. No comment markers or data attributes are needed — the runtime
|
|
472
|
-
resolves ordinary buffered SSR nodes via
|
|
529
|
+
resolves ordinary buffered SSR nodes via the lockstep hydration walk.
|
|
473
530
|
Progressive streaming uses temporary checkpoint scaffolding only to delay
|
|
474
531
|
activation until a complete region arrives; it removes that scaffolding after
|
|
475
532
|
commit.
|
|
@@ -505,7 +562,7 @@ flowchart LR
|
|
|
505
562
|
|
|
506
563
|
```mermaid
|
|
507
564
|
graph TD
|
|
508
|
-
EL["element.ts (~850 lines)<br/><i>Orchestrator</i><br/>$mount, $wire, $hydrate,<br
|
|
565
|
+
EL["element.ts (~850 lines)<br/><i>Orchestrator</i><br/>$mount, $wire, $hydrate,<br/>buildSSRIndex, $applySSRState,<br/>$update, events, cleanup"]
|
|
509
566
|
|
|
510
567
|
DIFF["element/diff.ts<br/><i>List Reconciliation</i><br/>positional + explicit-key diffing<br/>for <for> repeat blocks"]
|
|
511
568
|
|
|
@@ -556,7 +613,7 @@ sequenceDiagram
|
|
|
556
613
|
FW->>FW: SSR DOM detected (shadow root or children exist)
|
|
557
614
|
FW->>FW: $applySSRState() — seed decorated state
|
|
558
615
|
FW->>FW: $hydrate() — template-parallel path resolution
|
|
559
|
-
FW->>FW:
|
|
616
|
+
FW->>FW: buildSSRIndex() — number SSR nodes in one pre-order walk
|
|
560
617
|
FW->>FW: $wireEvents() + $wireRefs()
|
|
561
618
|
FW->>FW: $buildPathIndex(), $ready = true
|
|
562
619
|
FW->>CE: hydratedCallback() (once)
|
|
@@ -605,7 +662,7 @@ interface TemplateMeta {
|
|
|
605
662
|
ag?: [path, start, count][]; // Attribute target groups
|
|
606
663
|
c?: [conditionAST, blockIndex, slot][]; // Conditional blocks
|
|
607
664
|
r?: [collection, itemVar, blockIdx, slot][]; // Repeat blocks
|
|
608
|
-
eg?: [event, [[handler, argSpecs,
|
|
665
|
+
eg?: [event, [[handler, argSpecs, targetIndex, usesEvent?]]][]; // Events
|
|
609
666
|
b?: TemplateBlockMeta[]; // Nested block metadata
|
|
610
667
|
sa?: string; // Adopted stylesheet specifier
|
|
611
668
|
sd?: 1; // Shadow DOM flag for client-created
|
|
@@ -760,7 +817,7 @@ valid identity is re-established.
|
|
|
760
817
|
|
|
761
818
|
On initial hydration, the repeat system walks existing SSR children and
|
|
762
819
|
reconstructs collection instances by matching them against the compiled
|
|
763
|
-
template via
|
|
820
|
+
template via the `buildSSRIndex` walk. State is already seeded from
|
|
764
821
|
`window.__webui.state`, so repeat items reflect the server-rendered list
|
|
765
822
|
without parsing marker comments.
|
|
766
823
|
|
|
@@ -785,40 +842,44 @@ stylesheet specifier for a component.
|
|
|
785
842
|
## Path-Based Binding Resolution
|
|
786
843
|
|
|
787
844
|
Unlike frameworks that use comment markers or data attributes to locate each
|
|
788
|
-
dynamic binding, this framework uses **compiled
|
|
789
|
-
|
|
790
|
-
tree. Progressive streaming's temporary boundary markers locate complete
|
|
845
|
+
dynamic binding, this framework uses **compiled element indices** — each
|
|
846
|
+
binding names its element by pre-order position within its compiled section. Progressive streaming's temporary boundary markers locate complete
|
|
791
847
|
activation regions, not individual bindings.
|
|
792
848
|
|
|
793
|
-
### Client-created resolution (
|
|
849
|
+
### Client-created resolution (`collectTemplateElements`)
|
|
794
850
|
|
|
795
|
-
For client-created components
|
|
796
|
-
from the parsed template fragment)
|
|
797
|
-
|
|
851
|
+
For client-created components the DOM matches `meta.h` exactly (it was cloned
|
|
852
|
+
from the parsed template fragment), so a plain pre-order walk reproduces the
|
|
853
|
+
compiled numbering:
|
|
798
854
|
|
|
799
855
|
```typescript
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
856
|
+
// elements[0] is the section root; elements[i] is the i-th element
|
|
857
|
+
// a depth-first walk of the template meets.
|
|
858
|
+
const elements = collectTemplateElements(root);
|
|
859
|
+
const target = elements[index];
|
|
805
860
|
```
|
|
806
861
|
|
|
807
|
-
### SSR resolution (
|
|
862
|
+
### SSR resolution (`buildSSRIndex`)
|
|
808
863
|
|
|
809
|
-
SSR DOM
|
|
810
|
-
|
|
811
|
-
compiled template DOM **in
|
|
812
|
-
|
|
864
|
+
SSR DOM differs from the compiled template: the renderer strips inter-element
|
|
865
|
+
whitespace, and `<if>` / `<for>` bodies are rendered inline between markers.
|
|
866
|
+
`buildSSRIndex` walks the SSR DOM and the compiled template DOM **in lockstep**,
|
|
867
|
+
skipping whole marker ranges, and numbers the result the same way:
|
|
813
868
|
|
|
814
869
|
```typescript
|
|
815
|
-
//
|
|
816
|
-
//
|
|
817
|
-
//
|
|
870
|
+
// Elements are paired positionally and numbered in pre-order.
|
|
871
|
+
// Structural ranges are skipped whole — that content belongs to
|
|
872
|
+
// the block's own metadata.
|
|
873
|
+
// Text is the exception: whitespace stripping means text nodes do
|
|
874
|
+
// not line up, so text slots still resolve by ordinal.
|
|
818
875
|
```
|
|
819
876
|
|
|
820
|
-
This
|
|
821
|
-
|
|
877
|
+
This eliminates all *per-binding* annotation: no `data-w-*` attributes, no
|
|
878
|
+
comment per text run, no DOM markers around individual bindings. What the SSR
|
|
879
|
+
server does emit is the five structural comments documented above
|
|
880
|
+
(`<!--wr-->`, `<!--wi-->`, `<!--/wr-->`, `<!--wc-->`, `<!--/wc-->`), which
|
|
881
|
+
delimit `<if>` / `<for>` bodies and are what the walk skips over and anchors
|
|
882
|
+
blocks on. They are removed once hydration completes.
|
|
822
883
|
|
|
823
884
|
---
|
|
824
885
|
|
|
@@ -826,7 +887,7 @@ This template-parallel traversal eliminates the need for any marker comments,
|
|
|
826
887
|
|
|
827
888
|
| Operation | Cost | Why |
|
|
828
889
|
|-----------|------|-----|
|
|
829
|
-
| Initial hydration | O(
|
|
890
|
+
| Initial hydration | O(nodes) | One pre-order walk numbers the subtree; each binding is then an index lookup |
|
|
830
891
|
| Reactive update | O(affected) | Per-path index skips unrelated bindings |
|
|
831
892
|
| Conditional toggle | O(block size) | Create/destroy a block instance |
|
|
832
893
|
| Repeat reconciliation | O(items) | Positional scan; explicit keys use a reusable map only when order changes |
|
|
@@ -852,10 +913,15 @@ The runtime exposes hydration timing via the Performance API:
|
|
|
852
913
|
|
|
853
914
|
```ts
|
|
854
915
|
window.addEventListener('webui:hydration-complete', () => {
|
|
855
|
-
console.log('
|
|
916
|
+
console.log('The startup hydration cohort is complete.');
|
|
856
917
|
});
|
|
857
918
|
```
|
|
858
919
|
|
|
920
|
+
Parser-startup lazy components hold this event only through their first
|
|
921
|
+
intersection result. Initially visible roots finish first; dormant roots do not
|
|
922
|
+
hold the one-shot event open or redispatch it later. Use `hydratedCallback()`
|
|
923
|
+
for instance readiness.
|
|
924
|
+
|
|
859
925
|
---
|
|
860
926
|
|
|
861
927
|
## Where to Look Next
|
package/dist/decorators.js
CHANGED
|
@@ -163,8 +163,14 @@ function createReactiveProperty(proto, name, attrDefinition) {
|
|
|
163
163
|
},
|
|
164
164
|
set(newValue) {
|
|
165
165
|
const oldValue = this[backingKey];
|
|
166
|
-
if (Object.is(oldValue, newValue))
|
|
166
|
+
if (Object.is(oldValue, newValue)) {
|
|
167
|
+
if (this['$deferredSSR'] === true) {
|
|
168
|
+
const update = this['$update'];
|
|
169
|
+
if (update)
|
|
170
|
+
update.call(this, name);
|
|
171
|
+
}
|
|
167
172
|
return;
|
|
173
|
+
}
|
|
168
174
|
this[backingKey] = newValue;
|
|
169
175
|
if (attrDefinition && this['$ready'] === true) {
|
|
170
176
|
reflectPropertyToAttribute(this, attrDefinition, newValue);
|
|
@@ -173,7 +179,8 @@ function createReactiveProperty(proto, name, attrDefinition) {
|
|
|
173
179
|
if (typeof cb === 'function') {
|
|
174
180
|
cb.call(this, oldValue, newValue);
|
|
175
181
|
}
|
|
176
|
-
if (this.isConnected
|
|
182
|
+
if (this.isConnected ||
|
|
183
|
+
this['$deferredSSR'] === true) {
|
|
177
184
|
const upd = this['$update'];
|
|
178
185
|
if (upd)
|
|
179
186
|
upd.call(this, name);
|
|
@@ -7,4 +7,12 @@ export declare function collectItemMarkers(repeatStart: Comment): {
|
|
|
7
7
|
end: Comment | null;
|
|
8
8
|
};
|
|
9
9
|
export declare function nextElement(marker: Comment): Element | null;
|
|
10
|
+
export declare function skipBlockRange(start: Comment, data: string): ChildNode | null;
|
|
11
|
+
export declare function collectTemplateElements(root: Node): Array<Node | undefined>;
|
|
12
|
+
export interface SSRIndex {
|
|
13
|
+
elements: Array<Node | undefined>;
|
|
14
|
+
conds: Comment[];
|
|
15
|
+
repeats: Comment[];
|
|
16
|
+
}
|
|
17
|
+
export declare function buildSSRIndex(tplRoot: Node, ssrRoot: Node, needMarkers: boolean, ssrIsSectionChild?: boolean): SSRIndex;
|
|
10
18
|
export declare function findByOrdinal(parent: Node, nodeType: number, ordinal: number): Node | null;
|
package/dist/element/markers.js
CHANGED
|
@@ -43,6 +43,100 @@ export function nextElement(marker) {
|
|
|
43
43
|
}
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
|
+
export function skipBlockRange(start, data) {
|
|
47
|
+
const endTag = data === MARKER_COND_START ? MARKER_COND_END : MARKER_REPEAT_END;
|
|
48
|
+
let depth = 1;
|
|
49
|
+
let node = start.nextSibling;
|
|
50
|
+
while (node && depth > 0) {
|
|
51
|
+
if (node.nodeType === 8) {
|
|
52
|
+
const d = node.data;
|
|
53
|
+
if (d === data)
|
|
54
|
+
depth++;
|
|
55
|
+
else if (d === endTag)
|
|
56
|
+
depth--;
|
|
57
|
+
}
|
|
58
|
+
if (depth > 0)
|
|
59
|
+
node = node.nextSibling;
|
|
60
|
+
}
|
|
61
|
+
return node ? node.nextSibling : null;
|
|
62
|
+
}
|
|
63
|
+
export function collectTemplateElements(root) {
|
|
64
|
+
const elements = [root];
|
|
65
|
+
const stack = [root.firstChild];
|
|
66
|
+
while (stack.length > 0) {
|
|
67
|
+
const node = stack.pop();
|
|
68
|
+
let child = node ?? null;
|
|
69
|
+
while (child) {
|
|
70
|
+
if (child.nodeType === 1) {
|
|
71
|
+
elements.push(child);
|
|
72
|
+
if (child.firstChild) {
|
|
73
|
+
stack.push(child.nextSibling);
|
|
74
|
+
child = child.firstChild;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
child = child.nextSibling;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return elements;
|
|
82
|
+
}
|
|
83
|
+
export function buildSSRIndex(tplRoot, ssrRoot, needMarkers, ssrIsSectionChild = false) {
|
|
84
|
+
const elements = [ssrIsSectionChild ? undefined : ssrRoot];
|
|
85
|
+
const conds = [];
|
|
86
|
+
const repeats = [];
|
|
87
|
+
const stack = [
|
|
88
|
+
{
|
|
89
|
+
t: tplRoot.firstChild,
|
|
90
|
+
s: ssrIsSectionChild ? ssrRoot : ssrRoot.firstChild,
|
|
91
|
+
solo: ssrIsSectionChild,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
let index = 0;
|
|
95
|
+
while (stack.length > 0) {
|
|
96
|
+
const frame = stack[stack.length - 1];
|
|
97
|
+
let s = frame.s;
|
|
98
|
+
while (s) {
|
|
99
|
+
const type = s.nodeType;
|
|
100
|
+
if (type === 8) {
|
|
101
|
+
const data = s.data;
|
|
102
|
+
if (data === MARKER_COND_START) {
|
|
103
|
+
conds.push(s);
|
|
104
|
+
s = skipBlockRange(s, data);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (data === MARKER_REPEAT_START) {
|
|
108
|
+
repeats.push(s);
|
|
109
|
+
s = skipBlockRange(s, data);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (type === 1) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
s = s.nextSibling;
|
|
117
|
+
}
|
|
118
|
+
let t = frame.t;
|
|
119
|
+
while (t && t.nodeType !== 1)
|
|
120
|
+
t = t.nextSibling;
|
|
121
|
+
if (!t) {
|
|
122
|
+
stack.pop();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
index++;
|
|
126
|
+
if (s) {
|
|
127
|
+
elements[index] = s;
|
|
128
|
+
frame.s = frame.solo ? null : s.nextSibling;
|
|
129
|
+
}
|
|
130
|
+
frame.t = t.nextSibling;
|
|
131
|
+
if (t.firstChild) {
|
|
132
|
+
stack.push({ t: t.firstChild, s: s ? s.firstChild : null, solo: false });
|
|
133
|
+
}
|
|
134
|
+
else if (needMarkers && s && s.firstChild && t.tagName.indexOf('-') < 0) {
|
|
135
|
+
stack.push({ t: null, s: s.firstChild, solo: false });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { elements, conds, repeats };
|
|
139
|
+
}
|
|
46
140
|
export function findByOrdinal(parent, nodeType, ordinal) {
|
|
47
141
|
let count = 0;
|
|
48
142
|
let child = parent.firstChild;
|
|
@@ -50,22 +144,7 @@ export function findByOrdinal(parent, nodeType, ordinal) {
|
|
|
50
144
|
if (child.nodeType === 8) {
|
|
51
145
|
const data = child.data;
|
|
52
146
|
if (data === MARKER_COND_START || data === MARKER_REPEAT_START) {
|
|
53
|
-
|
|
54
|
-
let depth = 1;
|
|
55
|
-
child = child.nextSibling;
|
|
56
|
-
while (child && depth > 0) {
|
|
57
|
-
if (child.nodeType === 8) {
|
|
58
|
-
const d = child.data;
|
|
59
|
-
if (d === data)
|
|
60
|
-
depth++;
|
|
61
|
-
else if (d === endTag)
|
|
62
|
-
depth--;
|
|
63
|
-
}
|
|
64
|
-
if (depth > 0)
|
|
65
|
-
child = child.nextSibling;
|
|
66
|
-
}
|
|
67
|
-
if (child)
|
|
68
|
-
child = child.nextSibling;
|
|
147
|
+
child = skipBlockRange(child, data);
|
|
69
148
|
continue;
|
|
70
149
|
}
|
|
71
150
|
}
|
package/dist/element.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { TemplateElement } from './template-element.js';
|
|
2
|
-
import
|
|
2
|
+
import { LAZY_HYDRATION_ACTIVATE } from './lazy-hydration-contract.js';
|
|
3
|
+
import type { TemplateBlockMeta, TemplateMeta, TemplateNodeIndex } from './template.js';
|
|
3
4
|
import type { ScopeFrame, TemplateInstance } from './element/types.js';
|
|
4
5
|
export declare class WebUIElement extends TemplateElement {
|
|
6
|
+
private $lazyHydrationMode;
|
|
7
|
+
protected $shouldDeferSSRHydration(meta?: TemplateMeta): boolean;
|
|
8
|
+
protected $didDeferSSRHydration(): void;
|
|
9
|
+
protected $didDeferStreamedSSRHydration(state: Record<string, unknown> | undefined): void;
|
|
10
|
+
protected $activateDeferredSSR(state?: Record<string, unknown>): void;
|
|
11
|
+
private $resolveLazyHydrationMode;
|
|
12
|
+
[LAZY_HYDRATION_ACTIVATE](state: Record<string, unknown> | undefined): void;
|
|
13
|
+
disconnectedCallback(): void;
|
|
5
14
|
protected $observableNames(): Set<string>;
|
|
6
15
|
protected $shouldApplySSRState(key: string): boolean;
|
|
7
16
|
protected $syncAuthoredAttributes(): void;
|
|
8
17
|
$emit(name: string, detail?: unknown): boolean;
|
|
9
|
-
protected $finalize(instance: TemplateInstance, root: Node, meta: TemplateBlockMeta, resolver: (root: Node, path:
|
|
18
|
+
protected $finalize(instance: TemplateInstance, root: Node, meta: TemplateBlockMeta, resolver: (root: Node, path: TemplateNodeIndex) => Node | null, scope?: ScopeFrame): void;
|
|
10
19
|
private $wireEvents;
|
|
11
20
|
private $wireRoot;
|
|
12
21
|
private $addEvent;
|
package/dist/element.js
CHANGED
|
@@ -1,6 +1,92 @@
|
|
|
1
1
|
import { TemplateElement } from './template-element.js';
|
|
2
|
+
import { disconnectLazyHydration, isStreamedLazyActivation, isLazyHydrationCoordinatorInstalled, LAZY_HYDRATION_ACTIVATE, LAZY_HYDRATION_CONTENT_VISIBILITY, LAZY_HYDRATION_VIEWPORT, observeLazyHydration, observeStreamedLazyHydration, supportsLazyHydration, } from './lazy-hydration-contract.js';
|
|
3
|
+
import { isStreamingHydrationMode, STREAMED_HOST_ATTR, } from './streaming-mode.js';
|
|
2
4
|
import { attributeNameForProperty, getObservableNames, syncAttrProperties, } from './decorators.js';
|
|
5
|
+
const HYDRATE_ATTR = 'w-hydrate';
|
|
6
|
+
const RENDER_ATTR = 'w-render';
|
|
7
|
+
const HYDRATE_EAGER = 'eager';
|
|
8
|
+
const DEV = typeof __WEBUI_DEV__ === 'undefined' || __WEBUI_DEV__;
|
|
9
|
+
let warnedMissingLazyHydrationEntry = false;
|
|
10
|
+
function warnMissingLazyHydrationEntry(tag, policy) {
|
|
11
|
+
if (!DEV || warnedMissingLazyHydrationEntry)
|
|
12
|
+
return;
|
|
13
|
+
warnedMissingLazyHydrationEntry = true;
|
|
14
|
+
const directive = policy === LAZY_HYDRATION_CONTENT_VISIBILITY
|
|
15
|
+
? 'w-render="lazy"'
|
|
16
|
+
: 'w-hydrate="lazy"';
|
|
17
|
+
console.warn(`[WebUI] <${tag}> was compiled with \`${directive}\`, but ` +
|
|
18
|
+
"'@microsoft/webui-framework/lazy-hydration.js' was never imported, " +
|
|
19
|
+
'so it hydrated eagerly instead. Import that optional entry once before ' +
|
|
20
|
+
'your component definitions to enable visibility-deferred hydration.');
|
|
21
|
+
}
|
|
3
22
|
export class WebUIElement extends TemplateElement {
|
|
23
|
+
$shouldDeferSSRHydration(meta) {
|
|
24
|
+
this.$lazyHydrationMode = this.$resolveLazyHydrationMode(meta);
|
|
25
|
+
return this.$lazyHydrationMode !== undefined;
|
|
26
|
+
}
|
|
27
|
+
$didDeferSSRHydration() {
|
|
28
|
+
if (!isStreamingHydrationMode() ||
|
|
29
|
+
!this.hasAttribute(STREAMED_HOST_ATTR)) {
|
|
30
|
+
this.$primeSSRStateForDeferral();
|
|
31
|
+
const mode = this.$lazyHydrationMode;
|
|
32
|
+
if (mode !== undefined)
|
|
33
|
+
observeLazyHydration(this, mode);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
$didDeferStreamedSSRHydration(state) {
|
|
37
|
+
const mode = this.$lazyHydrationMode;
|
|
38
|
+
if (mode === undefined) {
|
|
39
|
+
super.$didDeferStreamedSSRHydration(state);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
observeStreamedLazyHydration(this, state, mode);
|
|
43
|
+
}
|
|
44
|
+
$activateDeferredSSR(state) {
|
|
45
|
+
if (isStreamedLazyActivation(this)) {
|
|
46
|
+
this.$lazyHydrationMode = undefined;
|
|
47
|
+
super.$activateDeferredSSRFromBoundary(state);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const mode = this.$lazyHydrationMode ??
|
|
51
|
+
this.$resolveLazyHydrationMode(this.$currentTemplateMetadata());
|
|
52
|
+
if (isStreamingHydrationMode() &&
|
|
53
|
+
this.hasAttribute(STREAMED_HOST_ATTR) &&
|
|
54
|
+
mode !== undefined) {
|
|
55
|
+
this.$lazyHydrationMode = mode;
|
|
56
|
+
observeStreamedLazyHydration(this, state, mode);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.$lazyHydrationMode = undefined;
|
|
60
|
+
super.$activateDeferredSSR(state);
|
|
61
|
+
}
|
|
62
|
+
$resolveLazyHydrationMode(meta) {
|
|
63
|
+
const policy = meta?.wp;
|
|
64
|
+
if (policy === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (policy === LAZY_HYDRATION_CONTENT_VISIBILITY &&
|
|
67
|
+
this.getAttribute(RENDER_ATTR) === HYDRATE_EAGER)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (this.getAttribute(HYDRATE_ATTR) === HYDRATE_EAGER)
|
|
70
|
+
return undefined;
|
|
71
|
+
if (!isLazyHydrationCoordinatorInstalled()) {
|
|
72
|
+
warnMissingLazyHydrationEntry(this.tagName, policy);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (!supportsLazyHydration())
|
|
76
|
+
return undefined;
|
|
77
|
+
return policy === LAZY_HYDRATION_CONTENT_VISIBILITY
|
|
78
|
+
? LAZY_HYDRATION_CONTENT_VISIBILITY
|
|
79
|
+
: LAZY_HYDRATION_VIEWPORT;
|
|
80
|
+
}
|
|
81
|
+
[LAZY_HYDRATION_ACTIVATE](state) {
|
|
82
|
+
this.$activateDeferredSSR(state);
|
|
83
|
+
}
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
if (this.$lazyHydrationMode !== undefined) {
|
|
86
|
+
disconnectLazyHydration(this);
|
|
87
|
+
}
|
|
88
|
+
super.disconnectedCallback();
|
|
89
|
+
}
|
|
4
90
|
$observableNames() {
|
|
5
91
|
return getObservableNames(this.constructor);
|
|
6
92
|
}
|
|
@@ -41,9 +127,8 @@ export class WebUIElement extends TemplateElement {
|
|
|
41
127
|
}
|
|
42
128
|
}
|
|
43
129
|
$wireRoot(instance, re) {
|
|
44
|
-
const target = this.shadowRoot ?? this;
|
|
45
130
|
for (let i = 0; i < re.length; i++) {
|
|
46
|
-
this.$addEvent(instance,
|
|
131
|
+
this.$addEvent(instance, this, re[i][0], re[i][1], re[i][2], undefined);
|
|
47
132
|
}
|
|
48
133
|
}
|
|
49
134
|
$addEvent(instance, target, eventName, handlerName, args, scope) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const LAZY_HYDRATION_ACTIVATE: unique symbol;
|
|
2
|
+
export declare const LAZY_HYDRATION_VIEWPORT = 1;
|
|
3
|
+
export declare const LAZY_HYDRATION_CONTENT_VISIBILITY = 2;
|
|
4
|
+
export type LazyHydrationMode = typeof LAZY_HYDRATION_VIEWPORT | typeof LAZY_HYDRATION_CONTENT_VISIBILITY;
|
|
5
|
+
export type LazyHydrationTarget = HTMLElement & {
|
|
6
|
+
[LAZY_HYDRATION_ACTIVATE](state: Record<string, unknown> | undefined): void;
|
|
7
|
+
};
|
|
8
|
+
export interface LazyHydrationCoordinator {
|
|
9
|
+
supportsLazyHydration(): boolean;
|
|
10
|
+
observe(target: LazyHydrationTarget, mode: LazyHydrationMode): void;
|
|
11
|
+
observeStreamed(target: LazyHydrationTarget, state: Record<string, unknown> | undefined, mode: LazyHydrationMode): void;
|
|
12
|
+
disconnect(target: LazyHydrationTarget): void;
|
|
13
|
+
isStreamedActivation(target: LazyHydrationTarget): boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerLazyHydrationCoordinator(impl: LazyHydrationCoordinator): void;
|
|
16
|
+
export declare function isLazyHydrationCoordinatorInstalled(): boolean;
|
|
17
|
+
export declare function supportsLazyHydration(): boolean;
|
|
18
|
+
export declare function observeLazyHydration(target: LazyHydrationTarget, mode: LazyHydrationMode): void;
|
|
19
|
+
export declare function observeStreamedLazyHydration(target: LazyHydrationTarget, state: Record<string, unknown> | undefined, mode: LazyHydrationMode): void;
|
|
20
|
+
export declare function disconnectLazyHydration(target: LazyHydrationTarget): void;
|
|
21
|
+
export declare function isStreamedLazyActivation(target: LazyHydrationTarget): boolean;
|
|
22
|
+
export declare function __resetLazyHydrationContractForTests(): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const LAZY_HYDRATION_ACTIVATE = Symbol();
|
|
2
|
+
export const LAZY_HYDRATION_VIEWPORT = 1;
|
|
3
|
+
export const LAZY_HYDRATION_CONTENT_VISIBILITY = 2;
|
|
4
|
+
let coordinator;
|
|
5
|
+
export function registerLazyHydrationCoordinator(impl) {
|
|
6
|
+
coordinator = impl;
|
|
7
|
+
}
|
|
8
|
+
export function isLazyHydrationCoordinatorInstalled() {
|
|
9
|
+
return coordinator !== undefined;
|
|
10
|
+
}
|
|
11
|
+
export function supportsLazyHydration() {
|
|
12
|
+
return coordinator?.supportsLazyHydration() ?? false;
|
|
13
|
+
}
|
|
14
|
+
export function observeLazyHydration(target, mode) {
|
|
15
|
+
coordinator?.observe(target, mode);
|
|
16
|
+
}
|
|
17
|
+
export function observeStreamedLazyHydration(target, state, mode) {
|
|
18
|
+
coordinator?.observeStreamed(target, state, mode);
|
|
19
|
+
}
|
|
20
|
+
export function disconnectLazyHydration(target) {
|
|
21
|
+
coordinator?.disconnect(target);
|
|
22
|
+
}
|
|
23
|
+
export function isStreamedLazyActivation(target) {
|
|
24
|
+
return coordinator?.isStreamedActivation(target) ?? false;
|
|
25
|
+
}
|
|
26
|
+
export function __resetLazyHydrationContractForTests() {
|
|
27
|
+
coordinator = undefined;
|
|
28
|
+
}
|