168
return parentContext;
169
}
170
171
-// This object is used to lazily reuse the ID of the first generated node, or assign one.
172
-// We can't assign an ID up front because the node we're attaching it to might already
173
-// have one. So we need to lazily use that if it's available.
174
-export type SuspenseBoundaryID = {
175
- formattedID: null | PrecomputedChunk,
176
-};
171
+export type SuspenseBoundaryID = null | PrecomputedChunk;
172
+
173
+export const UNINITIALIZED_SUSPENSE_BOUNDARY_ID: SuspenseBoundaryID = null;
174
178
-export function createSuspenseBoundaryID(
175
+export function assignSuspenseBoundaryID(
176
responseState: ResponseState,
177
): SuspenseBoundaryID {
181
- return {formattedID: null};
178
+ const generatedID = responseState.nextSuspenseID++;
179
+ return stringToPrecomputedChunk(
180
+ responseState.boundaryPrefix + generatedID.toString(16),
181
+ );
182
}
183
184
export type OpaqueIDType = string;
201
return escapeTextForBrowser(text);
202
}
203
204
-function assignAnID(
205
- responseState: ResponseState,
206
- id: SuspenseBoundaryID,
207
-): PrecomputedChunk {
208
- // TODO: This approach doesn't yield deterministic results since this is assigned during render.
209
- const generatedID = responseState.nextSuspenseID++;
210
- return (id.formattedID = stringToPrecomputedChunk(
211
- responseState.boundaryPrefix + generatedID.toString(16),
212
- ));
213
-}
214
-
215
-const dummyNode1 = stringToPrecomputedChunk('<template id="');
216
-const dummyNode2 = stringToPrecomputedChunk('"></template>');
217
-
218
-function pushDummyNodeWithID(
219
- target: Array<Chunk | PrecomputedChunk>,
220
- responseState: ResponseState,
221
- assignID: SuspenseBoundaryID,
222
-): void {
223
- const id = assignAnID(responseState, assignID);
224
- target.push(dummyNode1, id, dummyNode2);
225
-}
226
-
227
-export function pushEmpty(
228
- target: Array<Chunk | PrecomputedChunk>,
229
- responseState: ResponseState,
230
- assignID: null | SuspenseBoundaryID,
231
-): void {
232
- if (assignID !== null) {
233
- pushDummyNodeWithID(target, responseState, assignID);
234
- }
235
-}
236
-
204
const textSeparator = stringToPrecomputedChunk('<!-- -->');
205
206
export function pushTextInstance(
207
target: Array<Chunk | PrecomputedChunk>,
208
text: string,
209
responseState: ResponseState,
243
- assignID: null | SuspenseBoundaryID,
210
): void {
245
- if (assignID !== null) {
246
- pushDummyNodeWithID(target, responseState, assignID);
247
- }
211
if (text === '') {
212
// Empty text doesn't have a DOM node representation and the hydration is aware of this.
213
return;
477
const endOfStartTag = stringToPrecomputedChunk('>');
478
const endOfStartTagSelfClosing = stringToPrecomputedChunk('/>');
479
517
-const idAttr = stringToPrecomputedChunk(' id="');
518
-const attrEnd = stringToPrecomputedChunk('"');
519
-
520
-function pushID(
521
- target: Array<Chunk | PrecomputedChunk>,
522
- responseState: ResponseState,
523
- assignID: SuspenseBoundaryID,
524
- existingID: mixed,
525
-): void {
526
- if (
527
- existingID !== null &&
528
- existingID !== undefined &&
529
- (typeof existingID === 'string' || typeof existingID === 'object')
530
- ) {
531
- // We can reuse the existing ID for our purposes.
532
- assignID.formattedID = stringToPrecomputedChunk(
533
- escapeTextForBrowser(existingID),
534
- );
535
- } else {
536
- const encodedID = assignAnID(responseState, assignID);
537
- target.push(idAttr, encodedID, attrEnd);
538
- }
539
-}
540
-
480
function pushInnerHTML(
481
target: Array<Chunk | PrecomputedChunk>,
482
innerHTML,
537
target: Array<Chunk | PrecomputedChunk>,
538
props: Object,
539
responseState: ResponseState,
601
- assignID: null | SuspenseBoundaryID,
540
): ReactNodeList {
541
if (__DEV__) {
542
checkControlledValueProps('select', props);
589
}
590
}
591
}
654
- if (assignID !== null) {
655
- pushID(target, responseState, assignID, props.id);
656
- }
592
593
target.push(endOfStartTag);
594
pushInnerHTML(target, innerHTML, children);
628
props: Object,
629
responseState: ResponseState,
630
formatContext: FormatContext,
696
- assignID: null | SuspenseBoundaryID,
631
): ReactNodeList {
632
const selectedValue = formatContext.selectedValue;
633
710
target.push(selectedMarkerAttribute);
711
}
712
779
- if (assignID !== null) {
780
- pushID(target, responseState, assignID, props.id);
781
- }
782
-
713
target.push(endOfStartTag);
714
pushInnerHTML(target, innerHTML, children);
715
return children;
719
target: Array<Chunk | PrecomputedChunk>,
720
props: Object,
721
responseState: ResponseState,
792
- assignID: null | SuspenseBoundaryID,
722
): ReactNodeList {
723
if (__DEV__) {
724
checkControlledValueProps('input', props);
812
pushAttribute(target, responseState, 'value', defaultValue);
813
}
814
886
- if (assignID !== null) {
887
- pushID(target, responseState, assignID, props.id);
888
- }
889
-
815
target.push(endOfStartTagSelfClosing);
816
return null;
817
}
820
target: Array<Chunk | PrecomputedChunk>,
821
props: Object,
822
responseState: ResponseState,
898
- assignID: null | SuspenseBoundaryID,
823
): ReactNodeList {
824
if (__DEV__) {
825
checkControlledValueProps('textarea', props);
876
value = defaultValue;
877
}
878
955
- if (assignID !== null) {
956
- pushID(target, responseState, assignID, props.id);
957
- }
958
-
879
target.push(endOfStartTag);
880
881
// TODO (yungsters): Remove support for children content in <textarea>.
922
props: Object,
923
tag: string,
924
responseState: ResponseState,
1005
- assignID: null | SuspenseBoundaryID,
925
): ReactNodeList {
926
target.push(startChunkForTag(tag));
927
947
}
948
}
949
}
1031
- if (assignID !== null) {
1032
- pushID(target, responseState, assignID, props.id);
1033
- }
950
951
target.push(endOfStartTagSelfClosing);
952
return null;
956
target: Array<Chunk | PrecomputedChunk>,
957
props: Object,
958
responseState: ResponseState,
1043
- assignID: null | SuspenseBoundaryID,
959
): ReactNodeList {
960
target.push(startChunkForTag('menuitem'));
961
979
}
980
}
981
}
1067
- if (assignID !== null) {
1068
- pushID(target, responseState, assignID, props.id);
1069
- }
982
983
target.push(endOfStartTag);
984
return null;
989
props: Object,
990
tag: string,
991
responseState: ResponseState,
1080
- assignID: null | SuspenseBoundaryID,
992
): ReactNodeList {
993
target.push(startChunkForTag(tag));
994
1013
}
1014
}
1015
}
1105
- if (assignID !== null) {
1106
- pushID(target, responseState, assignID, props.id);
1107
- }
1016
1017
target.push(endOfStartTag);
1018
pushInnerHTML(target, innerHTML, children);
1030
props: Object,
1031
tag: string,
1032
responseState: ResponseState,
1125
- assignID: null | SuspenseBoundaryID,
1033
): ReactNodeList {
1034
target.push(startChunkForTag(tag));
1035
1073
}
1074
}
1075
}
1169
- if (assignID !== null) {
1170
- pushID(target, responseState, assignID, props.id);
1171
- }
1076
1077
target.push(endOfStartTag);
1078
pushInnerHTML(target, innerHTML, children);
1086
props: Object,
1087
tag: string,
1088
responseState: ResponseState,
1185
- assignID: null | SuspenseBoundaryID,
1089
): ReactNodeList {
1090
target.push(startChunkForTag(tag));
1091
1110
}
1111
}
1112
}
1210
- if (assignID !== null) {
1211
- pushID(target, responseState, assignID, props.id);
1212
- }
1113
1114
target.push(endOfStartTag);
1115
1175
props: Object,
1176
responseState: ResponseState,
1177
formatContext: FormatContext,
1278
- assignID: null | SuspenseBoundaryID,
1178
): ReactNodeList {
1179
if (__DEV__) {
1180
validateARIAProperties(type, props);
1216
switch (type) {
1217
// Special tags
1218
case 'select':
1320
- return pushStartSelect(target, props, responseState, assignID);
1219
+ return pushStartSelect(target, props, responseState);
1220
case 'option':
1322
- return pushStartOption(
1323
- target,
1324
- props,
1325
- responseState,
1326
- formatContext,
1327
- assignID,
1328
- );
1221
+ return pushStartOption(target, props, responseState, formatContext);
1222
case 'textarea':
1330
- return pushStartTextArea(target, props, responseState, assignID);
1223
+ return pushStartTextArea(target, props, responseState);
1224
case 'input':
1332
- return pushInput(target, props, responseState, assignID);
1225
+ return pushInput(target, props, responseState);
1226
case 'menuitem':
1334
- return pushStartMenuItem(target, props, responseState, assignID);
1227
+ return pushStartMenuItem(target, props, responseState);
1228
// Newline eating tags
1229
case 'listing':
1230
case 'pre': {
1338
- return pushStartPreformattedElement(
1339
- target,
1340
- props,
1341
- type,
1342
- responseState,
1343
- assignID,
1344
- );
1231
+ return pushStartPreformattedElement(target, props, type, responseState);
1232
}
1233
// Omitted close tags
1234
case 'area':
1245
case 'source':
1246
case 'track':
1247
case 'wbr': {
1361
- return pushSelfClosing(target, props, type, responseState, assignID);
1248
+ return pushSelfClosing(target, props, type, responseState);
1249
}
1250
// These are reserved SVG and MathML elements, that are never custom elements.
1251
// https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
1257
case 'font-face-format':
1258
case 'font-face-name':
1259
case 'missing-glyph': {
1373
- return pushStartGenericElement(
1374
- target,
1375
- props,
1376
- type,
1377
- responseState,
1378
- assignID,
1379
- );
1260
+ return pushStartGenericElement(target, props, type, responseState);
1261
}
1262
case 'html': {
1263
if (formatContext.insertionMode === ROOT_HTML_MODE) {
1266
// rendering the whole document.
1267
target.push(DOCTYPE);
1268
}
1388
- return pushStartGenericElement(
1389
- target,
1390
- props,
1391
- type,
1392
- responseState,
1393
- assignID,
1394
- );
1269
+ return pushStartGenericElement(target, props, type, responseState);
1270
}
1271
default: {
1272
if (type.indexOf('-') === -1 && typeof props.is !== 'string') {
1273
// Generic element
1399
- return pushStartGenericElement(
1400
- target,
1401
- props,
1402
- type,
1403
- responseState,
1404
- assignID,
1405
- );
1274
+ return pushStartGenericElement(target, props, type, responseState);
1275
} else {
1276
// Custom element
1408
- return pushStartCustomElement(
1409
- target,
1410
- props,
1411
- type,
1412
- responseState,
1413
- assignID,
1414
- );
1277
+ return pushStartCustomElement(target, props, type, responseState);
1278
}
1279
}
1280
}
1337
1338
// Suspense boundaries are encoded as comments.
1339
const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
1477
-const startPendingSuspenseBoundary = stringToPrecomputedChunk('<!--$?-->');
1340
+const startPendingSuspenseBoundary1 = stringToPrecomputedChunk(
1341
+ '<!--$?--><template id="',
1342
+);
1343
+const startPendingSuspenseBoundary2 = stringToPrecomputedChunk('"></template>');
1344
const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
1345
'<!--$!-->',
1346
);
1369
responseState: ResponseState,
1370
id: SuspenseBoundaryID,
1371
): boolean {
1506
- return writeChunk(destination, startPendingSuspenseBoundary);
1372
+ writeChunk(destination, startPendingSuspenseBoundary1);
1373
+ invariant(
1374
+ id !== null,
1375
+ 'An ID must have been assigned before we can complete the boundary.',
1376
+ );
1377
+ writeChunk(destination, id);
1378
+ return writeChunk(destination, startPendingSuspenseBoundary2);
1379
}
1380
export function writeStartClientRenderedSuspenseBoundary(
1381
destination: Destination,
1543
//
1544
// function clientRenderBoundary(suspenseBoundaryID) {
1545
// // Find the fallback's first element.
1674
-// let suspenseNode = document.getElementById(suspenseBoundaryID);
1675
-// if (!suspenseNode) {
1546
+// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
1547
+// if (!suspenseIdNode) {
1548
// // The user must have already navigated away from this tree.
1549
// // E.g. because the parent was hydrated.
1550
// return;
1551
// }
1680
-// // Find the boundary around the fallback. This might include text nodes.
1681
-// do {
1682
-// suspenseNode = suspenseNode.previousSibling;
1683
-// } while (
1684
-// suspenseNode.nodeType !== COMMENT_NODE ||
1685
-// suspenseNode.data !== SUSPENSE_PENDING_START_DATA
1686
-// );
1552
+// // Find the boundary around the fallback. This is always the previous node.
1553
+// const suspenseNode = suspenseIdNode.previousSibling;
1554
// // Tag it to be client rendered.
1555
// suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
1556
// // Tell React to retry it if the parent already hydrated.
1561
//
1562
// function completeBoundary(suspenseBoundaryID, contentID) {
1563
// // Find the fallback's first element.
1697
-// let suspenseNode = document.getElementById(suspenseBoundaryID);
1564
+// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
1565
// const contentNode = document.getElementById(contentID);
1566
// // We'll detach the content node so that regardless of what happens next we don't leave in the tree.
1567
// // This might also help by not causing recalcing each time we move a child from here to the target.
1568
// contentNode.parentNode.removeChild(contentNode);
1702
-// if (!suspenseNode) {
1569
+// if (!suspenseIdNode) {
1570
// // The user must have already navigated away from this tree.
1571
// // E.g. because the parent was hydrated. That's fine there's nothing to do
1572
// // but we have to make sure that we already deleted the container node.
1573
// return;
1574
// }
1708
-// // Find the boundary around the fallback. This might include text nodes.
1709
-// do {
1710
-// suspenseNode = suspenseNode.previousSibling;
1711
-// } while (
1712
-// suspenseNode.nodeType !== COMMENT_NODE ||
1713
-// suspenseNode.data !== SUSPENSE_PENDING_START_DATA
1714
-// );
1575
+// // Find the boundary around the fallback. This is always the previous node.
1576
+// const suspenseNode = suspenseIdNode.previousSibling;
1577
//
1578
// // Clear all the existing children. This is complicated because
1579
// // there can be embedded Suspense boundaries in the fallback.
1636
// }
1637
1638
const completeSegmentFunction =
1777
- 'function $RS(b,f){var a=document.getElementById(b),c=document.getElementById(f);for(a.parentNode.removeChild(a);a.firstChild;)c.parentNode.insertBefore(a.firstChild,c);c.parentNode.removeChild(c)}';
1639
+ 'function $RS(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)}';
1640
const completeBoundaryFunction =
1779
- 'function $RC(b,f){var a=document.getElementById(b),c=document.getElementById(f);c.parentNode.removeChild(c);if(a){do a=a.previousSibling;while(8!==a.nodeType||"$?"!==a.data);var h=a.parentNode,d=a.nextSibling,g=0;do{if(d&&8===d.nodeType){var e=d.data;if("/$"===e)if(0===g)break;else g--;else"$"!==e&&"$?"!==e&&"$!"!==e||g++}e=d.nextSibling;h.removeChild(d);d=e}while(d);for(;c.firstChild;)h.insertBefore(c.firstChild,d);a.data="$";a._reactRetry&&a._reactRetry()}}';
1641
+ 'function $RC(a,b){a=document.getElementById(a);b=document.getElementById(b);b.parentNode.removeChild(b);if(a){a=a.previousSibling;var f=a.parentNode,c=a.nextSibling,e=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d)if(0===e)break;else e--;else"$"!==d&&"$?"!==d&&"$!"!==d||e++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;b.firstChild;)f.insertBefore(b.firstChild,c);a.data="$";a._reactRetry&&a._reactRetry()}}';
1642
const clientRenderFunction =
1781
- 'function $RX(b){if(b=document.getElementById(b)){do b=b.previousSibling;while(8!==b.nodeType||"$?"!==b.data);b.data="$!";b._reactRetry&&b._reactRetry()}}';
1643
+ 'function $RX(a){if(a=document.getElementById(a))a=a.previousSibling,a.data="$!",a._reactRetry&&a._reactRetry()}';
1644
1645
const completeSegmentScript1Full = stringToPrecomputedChunk(
1646
'<script>' + completeSegmentFunction + ';$RS("',
1694
// Future calls can just reuse the same function.
1695
writeChunk(destination, completeBoundaryScript1Partial);
1696
}
1835
- const formattedBoundaryID = boundaryID.formattedID;
1697
invariant(
1837
- formattedBoundaryID !== null,
1698
+ boundaryID !== null,
1699
'An ID must have been assigned before we can complete the boundary.',
1700
);
1701
const formattedContentID = stringToChunk(contentSegmentID.toString(16));
1841
- writeChunk(destination, formattedBoundaryID);
1702
+ writeChunk(destination, boundaryID);
1703
writeChunk(destination, completeBoundaryScript2);
1704
writeChunk(destination, responseState.segmentPrefix);
1705
writeChunk(destination, formattedContentID);
1725
// Future calls can just reuse the same function.
1726
writeChunk(destination, clientRenderScript1Partial);
1727
}
1867
- const formattedBoundaryID = boundaryID.formattedID;
1728
invariant(
1869
- formattedBoundaryID !== null,
1729
+ boundaryID !== null,
1730
'An ID must have been assigned before we can complete the boundary.',
1731
);
1872
- writeChunk(destination, formattedBoundaryID);
1732
+ writeChunk(destination, boundaryID);
1733
return writeChunk(destination, clientRenderScript2);
1734
}