@@ -24,6 +24,7 @@ import invariant from 'shared/invariant';
24
24
25
25
// Per response,
26
26
export type ResponseState = {
27
+ nextSuspenseID : number ,
27
28
sentCompleteSegmentFunction : boolean ,
28
29
sentCompleteBoundaryFunction : boolean ,
29
30
sentClientRenderFunction : boolean ,
@@ -32,6 +33,7 @@ export type ResponseState = {
32
33
// Allows us to keep track of what we've already written so we can refer back to it.
33
34
export function createResponseState ( ) : ResponseState {
34
35
return {
36
+ nextSuspenseID : 0 ,
35
37
sentCompleteSegmentFunction : false ,
36
38
sentCompleteBoundaryFunction : false ,
37
39
sentClientRenderFunction : false ,
@@ -42,13 +44,13 @@ export function createResponseState(): ResponseState {
42
44
// We can't assign an ID up front because the node we're attaching it to might already
43
45
// have one. So we need to lazily use that if it's available.
44
46
export type SuspenseBoundaryID = {
45
- id : null | string ,
47
+ formattedID : null | PrecomputedChunk ,
46
48
} ;
47
49
48
50
export function createSuspenseBoundaryID (
49
51
responseState : ResponseState ,
50
52
) : SuspenseBoundaryID {
51
- return { id : null } ;
53
+ return { formattedID : null } ;
52
54
}
53
55
54
56
function encodeHTMLIDAttribute ( value : string ) : string {
@@ -59,23 +61,84 @@ function encodeHTMLTextNode(text: string): string {
59
61
return escapeTextForBrowser ( text ) ;
60
62
}
61
63
64
+ function assignAnID (
65
+ responseState : ResponseState ,
66
+ id : SuspenseBoundaryID ,
67
+ ) : PrecomputedChunk {
68
+ // TODO: This approach doesn't yield deterministic results since this is assigned during render.
69
+ const generatedID = responseState . nextSuspenseID ++ ;
70
+ return ( id . formattedID = stringToPrecomputedChunk ( generatedID . toString ( 16 ) ) ) ;
71
+ }
72
+
73
+ const dummyNode1 = stringToPrecomputedChunk ( '<span hidden id="' ) ;
74
+ const dummyNode2 = stringToPrecomputedChunk ( '"></span>' ) ;
75
+
76
+ function pushDummyNodeWithID (
77
+ target : Array < Chunk | PrecomputedChunk > ,
78
+ responseState : ResponseState ,
79
+ assignID : SuspenseBoundaryID ,
80
+ ) : void {
81
+ const id = assignAnID ( responseState , assignID ) ;
82
+ target . push ( dummyNode1 , id , dummyNode2 ) ;
83
+ }
84
+
85
+ export function pushEmpty (
86
+ target : Array < Chunk | PrecomputedChunk > ,
87
+ responseState : ResponseState ,
88
+ assignID : null | SuspenseBoundaryID ,
89
+ ) : void {
90
+ if ( assignID !== null ) {
91
+ pushDummyNodeWithID ( target , responseState , assignID ) ;
92
+ }
93
+ }
94
+
62
95
export function pushTextInstance (
63
96
target : Array < Chunk | PrecomputedChunk > ,
64
97
text : string ,
98
+ responseState : ResponseState ,
99
+ assignID : null | SuspenseBoundaryID ,
65
100
) : void {
101
+ if ( assignID !== null ) {
102
+ pushDummyNodeWithID ( target , responseState , assignID ) ;
103
+ }
66
104
target . push ( stringToChunk ( encodeHTMLTextNode ( text ) ) ) ;
67
105
}
68
106
69
107
const startTag1 = stringToPrecomputedChunk ( '<' ) ;
70
108
const startTag2 = stringToPrecomputedChunk ( '>' ) ;
71
109
110
+ const idAttr = stringToPrecomputedChunk ( ' id="' ) ;
111
+ const attrEnd = stringToPrecomputedChunk ( '"' ) ;
112
+
72
113
export function pushStartInstance (
73
114
target : Array < Chunk | PrecomputedChunk > ,
74
115
type : string ,
75
116
props : Object ,
117
+ responseState : ResponseState ,
118
+ assignID : null | SuspenseBoundaryID ,
76
119
) : void {
77
120
// TODO: Figure out if it's self closing and everything else.
78
- target . push ( startTag1 , stringToChunk ( type ) , startTag2 ) ;
121
+ if ( assignID !== null ) {
122
+ let encodedID ;
123
+ if ( typeof props . id === 'string' ) {
124
+ // We can reuse the existing ID for our purposes.
125
+ encodedID = assignID . formattedID = stringToPrecomputedChunk (
126
+ encodeHTMLIDAttribute ( props . id ) ,
127
+ ) ;
128
+ } else {
129
+ encodedID = assignAnID ( responseState , assignID ) ;
130
+ }
131
+ target . push (
132
+ startTag1 ,
133
+ stringToChunk ( type ) ,
134
+ idAttr ,
135
+ encodedID ,
136
+ attrEnd ,
137
+ startTag2 ,
138
+ ) ;
139
+ } else {
140
+ target . push ( startTag1 , stringToChunk ( type ) , startTag2 ) ;
141
+ }
79
142
}
80
143
81
144
const endTag1 = stringToPrecomputedChunk ( '</' ) ;
@@ -337,13 +400,11 @@ export function writeCompletedBoundaryInstruction(
337
400
writeChunk ( destination , completeBoundaryScript1Partial ) ;
338
401
}
339
402
// TODO: Use the identifierPrefix option to make the prefix configurable.
403
+ const formattedBoundaryID = boundaryID . formattedID ;
340
404
invariant (
341
- boundaryID . id !== null ,
405
+ formattedBoundaryID !== null ,
342
406
'An ID must have been assigned before we can complete the boundary.' ,
343
407
) ;
344
- const formattedBoundaryID = stringToChunk (
345
- encodeHTMLIDAttribute ( boundaryID . id ) ,
346
- ) ;
347
408
const formattedContentID = stringToChunk ( contentSegmentID . toString ( 16 ) ) ;
348
409
writeChunk ( destination , formattedBoundaryID ) ;
349
410
writeChunk ( destination , completeBoundaryScript2 ) ;
@@ -370,13 +431,11 @@ export function writeClientRenderBoundaryInstruction(
370
431
// Future calls can just reuse the same function.
371
432
writeChunk ( destination , clientRenderScript1Partial ) ;
372
433
}
434
+ const formattedBoundaryID = boundaryID . formattedID ;
373
435
invariant (
374
- boundaryID . id !== null ,
436
+ formattedBoundaryID !== null ,
375
437
'An ID must have been assigned before we can complete the boundary.' ,
376
438
) ;
377
- const formattedBoundaryID = stringToPrecomputedChunk (
378
- encodeHTMLIDAttribute ( boundaryID . id ) ,
379
- ) ;
380
439
writeChunk ( destination , formattedBoundaryID ) ;
381
440
return writeChunk ( destination , clientRenderScript2 ) ;
382
441
}
0 commit comments