@@ -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,86 @@ 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 (
71
+ 'B:' + generatedID . toString ( 16 ) ,
72
+ ) ) ;
73
+ }
74
+
75
+ const dummyNode1 = stringToPrecomputedChunk ( '<span hidden id="' ) ;
76
+ const dummyNode2 = stringToPrecomputedChunk ( '"></span>' ) ;
77
+
78
+ function pushDummyNodeWithID (
79
+ target : Array < Chunk | PrecomputedChunk > ,
80
+ responseState : ResponseState ,
81
+ assignID : SuspenseBoundaryID ,
82
+ ) : void {
83
+ const id = assignAnID ( responseState , assignID ) ;
84
+ target . push ( dummyNode1 , id , dummyNode2 ) ;
85
+ }
86
+
87
+ export function pushEmpty (
88
+ target : Array < Chunk | PrecomputedChunk > ,
89
+ responseState : ResponseState ,
90
+ assignID : null | SuspenseBoundaryID ,
91
+ ) : void {
92
+ if ( assignID !== null ) {
93
+ pushDummyNodeWithID ( target , responseState , assignID ) ;
94
+ }
95
+ }
96
+
62
97
export function pushTextInstance (
63
98
target : Array < Chunk | PrecomputedChunk > ,
64
99
text : string ,
100
+ responseState : ResponseState ,
101
+ assignID : null | SuspenseBoundaryID ,
65
102
) : void {
103
+ if ( assignID !== null ) {
104
+ pushDummyNodeWithID ( target , responseState , assignID ) ;
105
+ }
66
106
target . push ( stringToChunk ( encodeHTMLTextNode ( text ) ) ) ;
67
107
}
68
108
69
109
const startTag1 = stringToPrecomputedChunk ( '<' ) ;
70
110
const startTag2 = stringToPrecomputedChunk ( '>' ) ;
71
111
112
+ const idAttr = stringToPrecomputedChunk ( ' id="' ) ;
113
+ const attrEnd = stringToPrecomputedChunk ( '"' ) ;
114
+
72
115
export function pushStartInstance (
73
116
target : Array < Chunk | PrecomputedChunk > ,
74
117
type : string ,
75
118
props : Object ,
119
+ responseState : ResponseState ,
120
+ assignID : null | SuspenseBoundaryID ,
76
121
) : void {
77
122
// TODO: Figure out if it's self closing and everything else.
78
- target . push ( startTag1 , stringToChunk ( type ) , startTag2 ) ;
123
+ if ( assignID !== null ) {
124
+ let encodedID ;
125
+ if ( typeof props . id === 'string' ) {
126
+ // We can reuse the existing ID for our purposes.
127
+ encodedID = assignID . formattedID = stringToPrecomputedChunk (
128
+ encodeHTMLIDAttribute ( props . id ) ,
129
+ ) ;
130
+ } else {
131
+ encodedID = assignAnID ( responseState , assignID ) ;
132
+ }
133
+ target . push (
134
+ startTag1 ,
135
+ stringToChunk ( type ) ,
136
+ idAttr ,
137
+ encodedID ,
138
+ attrEnd ,
139
+ startTag2 ,
140
+ ) ;
141
+ } else {
142
+ target . push ( startTag1 , stringToChunk ( type ) , startTag2 ) ;
143
+ }
79
144
}
80
145
81
146
const endTag1 = stringToPrecomputedChunk ( '</' ) ;
@@ -337,13 +402,11 @@ export function writeCompletedBoundaryInstruction(
337
402
writeChunk ( destination , completeBoundaryScript1Partial ) ;
338
403
}
339
404
// TODO: Use the identifierPrefix option to make the prefix configurable.
405
+ const formattedBoundaryID = boundaryID . formattedID ;
340
406
invariant (
341
- boundaryID . id !== null ,
407
+ formattedBoundaryID !== null ,
342
408
'An ID must have been assigned before we can complete the boundary.' ,
343
409
) ;
344
- const formattedBoundaryID = stringToChunk (
345
- encodeHTMLIDAttribute ( boundaryID . id ) ,
346
- ) ;
347
410
const formattedContentID = stringToChunk ( contentSegmentID . toString ( 16 ) ) ;
348
411
writeChunk ( destination , formattedBoundaryID ) ;
349
412
writeChunk ( destination , completeBoundaryScript2 ) ;
@@ -370,13 +433,11 @@ export function writeClientRenderBoundaryInstruction(
370
433
// Future calls can just reuse the same function.
371
434
writeChunk ( destination , clientRenderScript1Partial ) ;
372
435
}
436
+ const formattedBoundaryID = boundaryID . formattedID ;
373
437
invariant (
374
- boundaryID . id !== null ,
438
+ formattedBoundaryID !== null ,
375
439
'An ID must have been assigned before we can complete the boundary.' ,
376
440
) ;
377
- const formattedBoundaryID = stringToPrecomputedChunk (
378
- encodeHTMLIDAttribute ( boundaryID . id ) ,
379
- ) ;
380
441
writeChunk ( destination , formattedBoundaryID ) ;
381
442
return writeChunk ( destination , clientRenderScript2 ) ;
382
443
}
0 commit comments