50
} from 'react-server/src/ReactServerStreamConfig';
51
import {
52
resolveRequest,
53
- getResources,
53
+ getResumableState,
54
flushResources,
55
} from 'react-server/src/ReactFizzServer';
56
117
const SentStyleInsertionFunction /* */ = 0b01000;
118
const SentFormReplayingRuntime /* */ = 0b10000;
119
120
-// Per response, global state that is not contextual to the rendering subtree.
121
-export type ResponseState = {
122
- bootstrapChunks: Array<Chunk | PrecomputedChunk>,
120
+// Per request, global state that is not contextual to the rendering subtree.
121
+// This cannot be resumed and therefore should only contain things that are
122
+// temporary working state or are never used in the prerender pass.
123
+export type RenderState = {
124
+ // These can be recreated from resumable state.
125
placeholderPrefix: PrecomputedChunk,
126
segmentPrefix: PrecomputedChunk,
127
boundaryPrefix: string,
126
- idPrefix: string,
127
- nextSuspenseID: number,
128
- streamingFormat: StreamingFormat,
128
130
- // state for script streaming format, unused if using external runtime / data
129
+ // inline script streaming format, unused if using external runtime / data
130
startInlineScript: PrecomputedChunk,
132
- instructions: InstructionState,
131
134
- // state for data streaming format
135
- externalRuntimeScript: null | ExternalRuntimeScript,
132
+ // the preamble must always flush before resuming, so all these chunks must
133
+ // be null or empty when resuming.
134
137
- // preamble and postamble chunks and state
135
+ // preamble chunks
136
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
137
headChunks: null | Array<Chunk | PrecomputedChunk>,
140
- hasBody: boolean,
138
139
// Hoistable chunks
140
charsetChunks: Array<Chunk | PrecomputedChunk>,
142
preloadChunks: Array<Chunk | PrecomputedChunk>,
143
hoistableChunks: Array<Chunk | PrecomputedChunk>,
144
145
+ // Module-global-like reference for current boundary resources
146
+ boundaryResources: ?BoundaryResources,
147
+
148
// Module-global-like reference for flushing/hoisting state of style resources
149
// We need to track whether the current request has flushed any style resources
150
// without sending an instruction to hoist them. we do that here
155
...
156
};
157
158
+// Per response, global state that is not contextual to the rendering subtree.
159
+// This is resumable and therefore should be serializable.
160
+export type ResumableState = {
161
+ // external runtime script chunks
162
+ externalRuntimeScript: null | ExternalRuntimeScript, // TODO: Move to a serializable format
163
+ bootstrapChunks: Array<Chunk | PrecomputedChunk>, // TODO: Move to a serializable format.
164
+ idPrefix: string,
165
+ nextSuspenseID: number,
166
+ streamingFormat: StreamingFormat,
167
+
168
+ // state for script streaming format, unused if using external runtime / data
169
+ instructions: InstructionState,
170
+
171
+ // postamble state
172
+ hasBody: boolean,
173
+ hasHtml: boolean,
174
+
175
+ // Resources
176
+
177
+ // Request local cache
178
+ preloadsMap: Map<string, PreloadResource>,
179
+ preconnectsMap: Map<string, PreconnectResource>,
180
+ stylesMap: Map<string, StyleResource>,
181
+ scriptsMap: Map<string, ScriptResource>,
182
+
183
+ // Flushing queues for Resource dependencies
184
+ preconnects: Set<PreconnectResource>,
185
+ fontPreloads: Set<PreloadResource>,
186
+ highImagePreloads: Set<PreloadResource>,
187
+ // usedImagePreloads: Set<PreloadResource>,
188
+ precedences: Map<string, Set<StyleResource>>,
189
+ stylePrecedences: Map<string, StyleTagResource>,
190
+ bootstrapScripts: Set<PreloadResource>,
191
+ scripts: Set<ScriptResource>,
192
+ bulkPreloads: Set<PreloadResource>,
193
+};
194
+
195
const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');
196
197
const startInlineScript = stringToPrecomputedChunk('<script>');
240
// Allows us to keep track of what we've already written so we can refer back to it.
241
// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
242
// is set, the server will send instructions via data attributes (instead of inline scripts)
206
-export function createResponseState(
207
- resources: Resources,
208
- identifierPrefix: string | void,
243
+export function createRenderState(
244
+ resumableState: ResumableState,
245
nonce: string | void,
210
- bootstrapScriptContent: string | void,
211
- bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
212
- bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
213
- externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
214
-): ResponseState {
215
- const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
246
+): RenderState {
247
const inlineScriptWithNonce =
248
nonce === undefined
249
? startInlineScript
250
: stringToPrecomputedChunk(
251
'<script nonce="' + escapeTextForBrowser(nonce) + '">',
252
);
253
+ const idPrefix = resumableState.idPrefix;
254
+ return {
255
+ placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),
256
+ segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
257
+ boundaryPrefix: idPrefix + 'B:',
258
+ startInlineScript: inlineScriptWithNonce,
259
+ htmlChunks: null,
260
+ headChunks: null,
261
+ charsetChunks: [],
262
+ preconnectChunks: [],
263
+ preloadChunks: [],
264
+ hoistableChunks: [],
265
+ nonce,
266
+ // like a module global for currently rendering boundary
267
+ boundaryResources: null,
268
+ stylesToHoist: false,
269
+ };
270
+}
271
+
272
+export function createResumableState(
273
+ identifierPrefix: string | void,
274
+ nonce: string | void,
275
+ bootstrapScriptContent: string | void,
276
+ bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
277
+ bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
278
+ externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
279
+): ResumableState {
280
+ const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
281
const bootstrapChunks: Array<Chunk | PrecomputedChunk> = [];
223
- let externalRuntimeScript: null | ExternalRuntimeScript = null;
282
let streamingFormat = ScriptStreamingFormat;
283
+ let externalRuntimeScript: null | ExternalRuntimeScript = null;
284
if (bootstrapScriptContent !== undefined) {
285
+ const inlineScriptWithNonce =
286
+ nonce === undefined
287
+ ? startInlineScript
288
+ : stringToPrecomputedChunk(
289
+ '<script nonce="' + escapeTextForBrowser(nonce) + '">',
290
+ );
291
bootstrapChunks.push(
292
inlineScriptWithNonce,
293
stringToChunk(escapeBootstrapScriptContent(bootstrapScriptContent)),
327
}
328
}
329
}
330
+
331
+ const resumableState: ResumableState = {
332
+ externalRuntimeScript: externalRuntimeScript,
333
+ bootstrapChunks: bootstrapChunks,
334
+ idPrefix: idPrefix,
335
+ nextSuspenseID: 0,
336
+ streamingFormat,
337
+ instructions: NothingSent,
338
+ hasBody: false,
339
+ hasHtml: false,
340
+
341
+ // @TODO add bootstrap script to implicit preloads
342
+
343
+ // persistent
344
+ preloadsMap: new Map(),
345
+ preconnectsMap: new Map(),
346
+ stylesMap: new Map(),
347
+ scriptsMap: new Map(),
348
+
349
+ // cleared on flush
350
+ preconnects: new Set(),
351
+ fontPreloads: new Set(),
352
+ highImagePreloads: new Set(),
353
+ // usedImagePreloads: new Set(),
354
+ precedences: new Map(),
355
+ stylePrecedences: new Map(),
356
+ bootstrapScripts: new Set(),
357
+ scripts: new Set(),
358
+ bulkPreloads: new Set(),
359
+ };
360
+
361
if (bootstrapScripts !== undefined) {
362
for (let i = 0; i < bootstrapScripts.length; i++) {
363
const scriptConfig = bootstrapScripts[i];
372
? 'use-credentials'
373
: '';
374
279
- preloadBootstrapScript(resources, src, nonce, integrity, crossOrigin);
375
+ preloadBootstrapScript(
376
+ resumableState,
377
+ src,
378
+ nonce,
379
+ integrity,
380
+ crossOrigin,
381
+ );
382
383
bootstrapChunks.push(
384
startScriptSrc,
419
? 'use-credentials'
420
: '';
421
320
- preloadBootstrapModule(resources, src, nonce, integrity, crossOrigin);
422
+ preloadBootstrapModule(
423
+ resumableState,
424
+ src,
425
+ nonce,
426
+ integrity,
427
+ crossOrigin,
428
+ );
429
430
bootstrapChunks.push(
431
startModuleSrc,
453
bootstrapChunks.push(endAsyncScript);
454
}
455
}
348
- return {
349
- bootstrapChunks: bootstrapChunks,
350
- placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),
351
- segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
352
- boundaryPrefix: idPrefix + 'B:',
353
- idPrefix: idPrefix,
354
- nextSuspenseID: 0,
355
- streamingFormat,
356
- startInlineScript: inlineScriptWithNonce,
357
- instructions: NothingSent,
358
- externalRuntimeScript,
359
- htmlChunks: null,
360
- headChunks: null,
361
- hasBody: false,
362
- charsetChunks: [],
363
- preconnectChunks: [],
364
- preloadChunks: [],
365
- hoistableChunks: [],
366
- stylesToHoist: false,
367
- nonce,
368
- };
456
+
457
+ return resumableState;
458
}
459
460
// Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion
595
export const UNINITIALIZED_SUSPENSE_BOUNDARY_ID: SuspenseBoundaryID = null;
596
597
export function assignSuspenseBoundaryID(
509
- responseState: ResponseState,
598
+ renderState: RenderState,
599
+ resumableState: ResumableState,
600
): SuspenseBoundaryID {
511
- const generatedID = responseState.nextSuspenseID++;
601
+ const generatedID = resumableState.nextSuspenseID++;
602
return stringToPrecomputedChunk(
513
- responseState.boundaryPrefix + generatedID.toString(16),
603
+ renderState.boundaryPrefix + generatedID.toString(16),
604
);
605
}
606
607
export function makeId(
518
- responseState: ResponseState,
608
+ resumableState: ResumableState,
609
treeId: string,
610
localId: number,
611
): string {
522
- const idPrefix = responseState.idPrefix;
612
+ const idPrefix = resumableState.idPrefix;
613
614
let id = ':' + idPrefix + 'R' + treeId;
615
632
export function pushTextInstance(
633
target: Array<Chunk | PrecomputedChunk>,
634
text: string,
545
- responseState: ResponseState,
635
+ renderState: RenderState,
636
textEmbedded: boolean,
637
): boolean {
638
if (text === '') {
650
// emit a text separator when we don't know for sure it is safe to omit
651
export function pushSegmentFinale(
652
target: Array<Chunk | PrecomputedChunk>,
563
- responseState: ResponseState,
653
+ renderState: RenderState,
654
lastPushedText: boolean,
655
textEmbedded: boolean,
656
): void {
790
}
791
}
792
703
-function makeFormFieldPrefix(responseState: ResponseState): string {
793
+function makeFormFieldPrefix(resumableState: ResumableState): string {
794
// I'm just reusing this counter. It's not really the same namespace as "name".
795
// It could just be its own counter.
706
- const id = responseState.nextSuspenseID++;
707
- return responseState.idPrefix + id;
796
+ const id = resumableState.nextSuspenseID++;
797
+ return resumableState.idPrefix + id;
798
}
799
800
// Since this will likely be repeated a lot in the HTML, we use a more concise message
838
839
function pushFormActionAttribute(
840
target: Array<Chunk | PrecomputedChunk>,
751
- responseState: ResponseState,
841
+ resumableState: ResumableState,
842
+ renderState: RenderState,
843
formAction: any,
844
formEncType: any,
845
formMethod: any,
879
if (typeof customAction === 'function') {
880
// This action has a custom progressive enhancement form that can submit the form
881
// back to the server if it's invoked before hydration. Such as a Server Action.
791
- const prefix = makeFormFieldPrefix(responseState);
882
+ const prefix = makeFormFieldPrefix(resumableState);
883
const customFields = formAction.$$FORM_ACTION(prefix);
884
name = customFields.name;
885
formAction = customFields.action || '';
905
formEncType = null;
906
formMethod = null;
907
formTarget = null;
817
- injectFormReplayingRuntime(responseState);
908
+ injectFormReplayingRuntime(resumableState, renderState);
909
}
910
}
911
if (name != null) {
1254
}
1255
}
1256
1166
-// TODO: Move these to ResponseState so that we warn for every request.
1257
+// TODO: Move these to RenderState so that we warn for every request.
1258
// It would help debugging in stateful servers (e.g. service worker).
1259
let didWarnDefaultInputValue = false;
1260
let didWarnDefaultChecked = false;
1482
const formReplayingRuntimeScript =
1483
stringToPrecomputedChunk(formReplayingRuntime);
1484
1394
-function injectFormReplayingRuntime(responseState: ResponseState): void {
1485
+function injectFormReplayingRuntime(
1486
+ resumableState: ResumableState,
1487
+ renderState: RenderState,
1488
+): void {
1489
// If we haven't sent it yet, inject the runtime that tracks submitted JS actions
1490
// for later replaying by Fiber. If we use an external runtime, we don't need
1491
// to emit anything. It's always used.
1492
if (
1399
- (responseState.instructions & SentFormReplayingRuntime) === NothingSent &&
1400
- (!enableFizzExternalRuntime || !responseState.externalRuntimeScript)
1493
+ (resumableState.instructions & SentFormReplayingRuntime) === NothingSent &&
1494
+ (!enableFizzExternalRuntime || !resumableState.externalRuntimeScript)
1495
) {
1402
- responseState.instructions |= SentFormReplayingRuntime;
1403
- responseState.bootstrapChunks.unshift(
1404
- responseState.startInlineScript,
1496
+ resumableState.instructions |= SentFormReplayingRuntime;
1497
+ resumableState.bootstrapChunks.unshift(
1498
+ renderState.startInlineScript,
1499
formReplayingRuntimeScript,
1500
endInlineScript,
1501
);
1505
function pushStartForm(
1506
target: Array<Chunk | PrecomputedChunk>,
1507
props: Object,
1414
- responseState: ResponseState,
1508
+ resumableState: ResumableState,
1509
+ renderState: RenderState,
1510
): ReactNodeList {
1511
target.push(startChunkForTag('form'));
1512
1577
if (typeof customAction === 'function') {
1578
// This action has a custom progressive enhancement form that can submit the form
1579
// back to the server if it's invoked before hydration. Such as a Server Action.
1485
- const prefix = makeFormFieldPrefix(responseState);
1580
+ const prefix = makeFormFieldPrefix(resumableState);
1581
const customFields = formAction.$$FORM_ACTION(prefix);
1582
formAction = customFields.action || '';
1583
formEncType = customFields.encType;
1602
formEncType = null;
1603
formMethod = null;
1604
formTarget = null;
1510
- injectFormReplayingRuntime(responseState);
1605
+ injectFormReplayingRuntime(resumableState, renderState);
1606
}
1607
}
1608
if (formAction != null) {
1640
function pushInput(
1641
target: Array<Chunk | PrecomputedChunk>,
1642
props: Object,
1548
- responseState: ResponseState,
1643
+ resumableState: ResumableState,
1644
+ renderState: RenderState,
1645
): ReactNodeList {
1646
if (__DEV__) {
1647
checkControlledValueProps('input', props);
1722
1723
const formData = pushFormActionAttribute(
1724
target,
1629
- responseState,
1725
+ resumableState,
1726
+ renderState,
1727
formAction,
1728
formEncType,
1729
formMethod,
1782
function pushStartButton(
1783
target: Array<Chunk | PrecomputedChunk>,
1784
props: Object,
1688
- responseState: ResponseState,
1785
+ resumableState: ResumableState,
1786
+ renderState: RenderState,
1787
): ReactNodeList {
1788
target.push(startChunkForTag('button'));
1789
1846
1847
const formData = pushFormActionAttribute(
1848
target,
1751
- responseState,
1849
+ resumableState,
1850
+ renderState,
1851
formAction,
1852
formEncType,
1853
formMethod,
1993
function pushMeta(
1994
target: Array<Chunk | PrecomputedChunk>,
1995
props: Object,
1897
- responseState: ResponseState,
1996
+ renderState: RenderState,
1997
textEmbedded: boolean,
1998
insertionMode: InsertionMode,
1999
noscriptTagInScope: boolean,
2013
}
2014
2015
if (typeof props.charSet === 'string') {
1917
- return pushSelfClosing(responseState.charsetChunks, props, 'meta');
2016
+ return pushSelfClosing(renderState.charsetChunks, props, 'meta');
2017
} else if (props.name === 'viewport') {
2018
// "viewport" isn't related to preconnect but it has the right priority
1920
- return pushSelfClosing(responseState.preconnectChunks, props, 'meta');
2019
+ return pushSelfClosing(renderState.preconnectChunks, props, 'meta');
2020
} else {
1922
- return pushSelfClosing(responseState.hoistableChunks, props, 'meta');
2021
+ return pushSelfClosing(renderState.hoistableChunks, props, 'meta');
2022
}
2023
}
2024
} else {
2029
function pushLink(
2030
target: Array<Chunk | PrecomputedChunk>,
2031
props: Object,
1933
- responseState: ResponseState,
1934
- resources: Resources,
2032
+ resumableState: ResumableState,
2033
+ renderState: RenderState,
2034
textEmbedded: boolean,
2035
insertionMode: InsertionMode,
2036
noscriptTagInScope: boolean,
2097
return pushLinkImpl(target, props);
2098
} else {
2099
// This stylesheet refers to a Resource and we create a new one if necessary
2001
- let resource = resources.stylesMap.get(key);
2100
+ let resource = resumableState.stylesMap.get(key);
2101
if (!resource) {
2102
const resourceProps = stylesheetPropsFromRawProps(props);
2004
- const preloadResource = resources.preloadsMap.get(key);
2103
+ const preloadResource = resumableState.preloadsMap.get(key);
2104
let state = NoState;
2105
if (preloadResource) {
2106
// If we already had a preload we don't want that resource to flush directly.
2120
state,
2121
props: resourceProps,
2122
};
2024
- resources.stylesMap.set(key, resource);
2025
- let precedenceSet = resources.precedences.get(precedence);
2123
+ resumableState.stylesMap.set(key, resource);
2124
+ let precedenceSet = resumableState.precedences.get(precedence);
2125
if (!precedenceSet) {
2126
precedenceSet = new Set();
2028
- resources.precedences.set(precedence, precedenceSet);
2127
+ resumableState.precedences.set(precedence, precedenceSet);
2128
const emptyStyleResource = {
2129
type: 'style',
2130
chunks: ([]: Array<Chunk | PrecomputedChunk>),
2136
};
2137
precedenceSet.add(emptyStyleResource);
2138
if (__DEV__) {
2040
- if (resources.stylePrecedences.has(precedence)) {
2139
+ if (resumableState.stylePrecedences.has(precedence)) {
2140
console.error(
2141
'React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.',
2142
precedence,
2143
);
2144
}
2145
}
2047
- resources.stylePrecedences.set(precedence, emptyStyleResource);
2146
+ resumableState.stylePrecedences.set(precedence, emptyStyleResource);
2147
}
2148
precedenceSet.add(resource);
2149
}
2051
- if (resources.boundaryResources) {
2052
- resources.boundaryResources.add(resource);
2150
+ if (renderState.boundaryResources) {
2151
+ renderState.boundaryResources.add(resource);
2152
}
2153
if (textEmbedded) {
2154
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
2173
switch (props.rel) {
2174
case 'preconnect':
2175
case 'dns-prefetch':
2077
- return pushLinkImpl(responseState.preconnectChunks, props);
2176
+ return pushLinkImpl(renderState.preconnectChunks, props);
2177
case 'preload':
2079
- return pushLinkImpl(responseState.preloadChunks, props);
2178
+ return pushLinkImpl(renderState.preloadChunks, props);
2179
default:
2081
- return pushLinkImpl(responseState.hoistableChunks, props);
2180
+ return pushLinkImpl(renderState.hoistableChunks, props);
2181
}
2182
}
2183
} else {
2218
function pushStyle(
2219
target: Array<Chunk | PrecomputedChunk>,
2220
props: Object,
2122
- resources: Resources,
2221
+ resumableState: ResumableState,
2222
+ renderState: RenderState,
2223
textEmbedded: boolean,
2224
insertionMode: InsertionMode,
2225
noscriptTagInScope: boolean,
2279
}
2280
2281
const key = getResourceKey('style', href);
2182
- let resource = resources.stylesMap.get(key);
2282
+ let resource = resumableState.stylesMap.get(key);
2283
if (!resource) {
2184
- resource = resources.stylePrecedences.get(precedence);
2284
+ resource = resumableState.stylePrecedences.get(precedence);
2285
if (!resource) {
2286
resource = {
2287
type: 'style',
2292
hrefs: [href],
2293
},
2294
};
2195
- resources.stylePrecedences.set(precedence, resource);
2295
+ resumableState.stylePrecedences.set(precedence, resource);
2296
const precedenceSet: Set<StyleResource> = new Set();
2297
precedenceSet.add(resource);
2298
if (__DEV__) {
2199
- if (resources.precedences.has(precedence)) {
2299
+ if (resumableState.precedences.has(precedence)) {
2300
console.error(
2301
'React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.',
2302
precedence,
2303
);
2304
}
2305
}
2206
- resources.precedences.set(precedence, precedenceSet);
2306
+ resumableState.precedences.set(precedence, precedenceSet);
2307
} else {
2308
resource.props.hrefs.push(href);
2309
}
2210
- resources.stylesMap.set(key, resource);
2211
- if (resources.boundaryResources) {
2212
- resources.boundaryResources.add(resource);
2310
+ resumableState.stylesMap.set(key, resource);
2311
+ if (renderState.boundaryResources) {
2312
+ renderState.boundaryResources.add(resource);
2313
}
2314
pushStyleContents(resource.chunks, props);
2315
}
2433
function pushImg(
2434
target: Array<Chunk | PrecomputedChunk>,
2435
props: Object,
2336
- resources: Resources,
2436
+ resumableState: ResumableState,
2437
): null {
2438
const {src, srcSet} = props;
2439
if (
2459
)
2460
) {
2461
// We have a suspensey image and ought to preload it to optimize the loading of display blocking
2362
- // resources.
2462
+ // resumableState.
2463
const {sizes} = props;
2464
const key = getImagePreloadKey(src, srcSet, sizes);
2365
- let resource = resources.preloadsMap.get(key);
2465
+ let resource = resumableState.preloadsMap.get(key);
2466
if (!resource) {
2467
resource = {
2468
type: 'preload',
2485
referrerPolicy: props.referrerPolicy,
2486
},
2487
};
2388
- resources.preloadsMap.set(key, resource);
2488
+ resumableState.preloadsMap.set(key, resource);
2489
pushLinkImpl(resource.chunks, resource.props);
2490
}
2491
if (
2492
props.fetchPriority === 'high' ||
2393
- resources.highImagePreloads.size < 10
2493
+ resumableState.highImagePreloads.size < 10
2494
) {
2395
- resources.highImagePreloads.add(resource);
2495
+ resumableState.highImagePreloads.add(resource);
2496
} else {
2397
- resources.bulkPreloads.add(resource);
2497
+ resumableState.bulkPreloads.add(resource);
2498
}
2499
}
2500
return pushSelfClosing(target, props, 'img');
2563
function pushTitle(
2564
target: Array<Chunk | PrecomputedChunk>,
2565
props: Object,
2466
- responseState: ResponseState,
2566
+ renderState: RenderState,
2567
insertionMode: InsertionMode,
2568
noscriptTagInScope: boolean,
2569
): ReactNodeList {
2621
!noscriptTagInScope &&
2622
props.itemProp == null
2623
) {
2524
- pushTitleImpl(responseState.hoistableChunks, props);
2624
+ pushTitleImpl(renderState.hoistableChunks, props);
2625
return null;
2626
} else {
2627
return pushTitleImpl(target, props);
2753
function pushStartHead(
2754
target: Array<Chunk | PrecomputedChunk>,
2755
props: Object,
2656
- responseState: ResponseState,
2756
+ renderState: RenderState,
2757
insertionMode: InsertionMode,
2758
): ReactNodeList {
2759
if (enableFloat) {
2660
- if (insertionMode < HTML_MODE && responseState.headChunks === null) {
2760
+ if (insertionMode < HTML_MODE && renderState.headChunks === null) {
2761
// This <head> is the Document.head and should be part of the preamble
2662
- responseState.headChunks = [];
2663
- return pushStartGenericElement(responseState.headChunks, props, 'head');
2762
+ renderState.headChunks = [];
2763
+ return pushStartGenericElement(renderState.headChunks, props, 'head');
2764
} else {
2765
// This <head> is deep and is likely just an error. we emit it inline though.
2766
// Validation should warn that this tag is the the wrong spot.
2774
function pushStartHtml(
2775
target: Array<Chunk | PrecomputedChunk>,
2776
props: Object,
2677
- responseState: ResponseState,
2777
+ renderState: RenderState,
2778
insertionMode: InsertionMode,
2779
): ReactNodeList {
2780
if (enableFloat) {
2681
- if (insertionMode === ROOT_HTML_MODE && responseState.htmlChunks === null) {
2781
+ if (insertionMode === ROOT_HTML_MODE && renderState.htmlChunks === null) {
2782
// This <html> is the Document.documentElement and should be part of the preamble
2683
- responseState.htmlChunks = [DOCTYPE];
2684
- return pushStartGenericElement(responseState.htmlChunks, props, 'html');
2783
+ renderState.htmlChunks = [DOCTYPE];
2784
+ return pushStartGenericElement(renderState.htmlChunks, props, 'html');
2785
} else {
2786
// This <html> is deep and is likely just an error. we emit it inline though.
2787
// Validation should warn that this tag is the the wrong spot.
2801
function pushScript(
2802
target: Array<Chunk | PrecomputedChunk>,
2803
props: Object,
2704
- resources: Resources,
2804
+ resumableState: ResumableState,
2805
textEmbedded: boolean,
2806
insertionMode: InsertionMode,
2807
noscriptTagInScope: boolean,
2829
const src = props.src;
2830
const key = getResourceKey('script', src);
2831
// We can make this <script> into a ScriptResource
2732
- let resource = resources.scriptsMap.get(key);
2832
+ let resource = resumableState.scriptsMap.get(key);
2833
if (!resource) {
2834
resource = {
2835
type: 'script',
2837
state: NoState,
2838
props: null,
2839
};
2740
- resources.scriptsMap.set(key, resource);
2840
+ resumableState.scriptsMap.set(key, resource);
2841
// Add to the script flushing queue
2742
- resources.scripts.add(resource);
2842
+ resumableState.scripts.add(resource);
2843
2844
let scriptProps = props;
2745
- const preloadResource = resources.preloadsMap.get(key);
2845
+ const preloadResource = resumableState.preloadsMap.get(key);
2846
if (preloadResource) {
2847
// If we already had a preload we don't want that resource to flush directly.
2848
// We let the newly created resource govern flushing.
3136
target: Array<Chunk | PrecomputedChunk>,
3137
type: string,
3138
props: Object,
3039
- resources: Resources,
3040
- responseState: ResponseState,
3139
+ resumableState: ResumableState,
3140
+ renderState: RenderState,
3141
formatContext: FormatContext,
3142
textEmbedded: boolean,
3143
): ReactNodeList {
3193
case 'textarea':
3194
return pushStartTextArea(target, props);
3195
case 'input':
3096
- return pushInput(target, props, responseState);
3196
+ return pushInput(target, props, resumableState, renderState);
3197
case 'button':
3098
- return pushStartButton(target, props, responseState);
3198
+ return pushStartButton(target, props, resumableState, renderState);
3199
case 'form':
3100
- return pushStartForm(target, props, responseState);
3200
+ return pushStartForm(target, props, resumableState, renderState);
3201
case 'menuitem':
3202
return pushStartMenuItem(target, props);
3203
case 'title':
3205
? pushTitle(
3206
target,
3207
props,
3108
- responseState,
3208
+ renderState,
3209
formatContext.insertionMode,
3210
formatContext.noscriptTagInScope,
3211
)
3214
return pushLink(
3215
target,
3216
props,
3117
- responseState,
3118
- resources,
3217
+ resumableState,
3218
+ renderState,
3219
textEmbedded,
3220
formatContext.insertionMode,
3221
formatContext.noscriptTagInScope,
3225
? pushScript(
3226
target,
3227
props,
3128
- resources,
3228
+ resumableState,
3229
textEmbedded,
3230
formatContext.insertionMode,
3231
formatContext.noscriptTagInScope,
3235
return pushStyle(
3236
target,
3237
props,
3138
- resources,
3238
+ resumableState,
3239
+ renderState,
3240
textEmbedded,
3241
formatContext.insertionMode,
3242
formatContext.noscriptTagInScope,
3245
return pushMeta(
3246
target,
3247
props,
3147
- responseState,
3248
+ renderState,
3249
textEmbedded,
3250
formatContext.insertionMode,
3251
formatContext.noscriptTagInScope,
3257
}
3258
case 'img': {
3259
return enableFloat
3159
- ? pushImg(target, props, resources)
3260
+ ? pushImg(target, props, resumableState)
3261
: pushSelfClosing(target, props, type);
3262
}
3263
// Omitted close tags
3291
return pushStartHead(
3292
target,
3293
props,
3193
- responseState,
3294
+ renderState,
3295
formatContext.insertionMode,
3296
);
3297
case 'html': {
3298
return pushStartHtml(
3299
target,
3300
props,
3200
- responseState,
3301
+ renderState,
3302
formatContext.insertionMode,
3303
);
3304
}
3320
target: Array<Chunk | PrecomputedChunk>,
3321
type: string,
3322
props: Object,
3222
- responseState: ResponseState,
3323
+ resumableState: ResumableState,
3324
formatContext: FormatContext,
3325
): void {
3326
switch (type) {
3364
// we won't emit any more tags
3365
case 'body': {
3366
if (enableFloat && formatContext.insertionMode <= HTML_HTML_MODE) {
3266
- responseState.hasBody = true;
3367
+ resumableState.hasBody = true;
3368
return;
3369
}
3370
break;
3371
}
3372
case 'html':
3373
if (enableFloat && formatContext.insertionMode === ROOT_HTML_MODE) {
3374
+ resumableState.hasHtml = true;
3375
return;
3376
}
3377
break;
3381
3382
function writeBootstrap(
3383
destination: Destination,
3282
- responseState: ResponseState,
3384
+ resumableState: ResumableState,
3385
): boolean {
3284
- const bootstrapChunks = responseState.bootstrapChunks;
3386
+ const bootstrapChunks = resumableState.bootstrapChunks;
3387
let i = 0;
3388
for (; i < bootstrapChunks.length - 1; i++) {
3389
writeChunk(destination, bootstrapChunks[i]);
3398
3399
export function writeCompletedRoot(
3400
destination: Destination,
3299
- responseState: ResponseState,
3401
+ resumableState: ResumableState,
3402
): boolean {
3301
- return writeBootstrap(destination, responseState);
3403
+ return writeBootstrap(destination, resumableState);
3404
}
3405
3406
// Structural Nodes
3412
const placeholder2 = stringToPrecomputedChunk('"></template>');
3413
export function writePlaceholder(
3414
destination: Destination,
3313
- responseState: ResponseState,
3415
+ renderState: RenderState,
3416
id: number,
3417
): boolean {
3418
writeChunk(destination, placeholder1);
3317
- writeChunk(destination, responseState.placeholderPrefix);
3419
+ writeChunk(destination, renderState.placeholderPrefix);
3420
const formattedID = stringToChunk(id.toString(16));
3421
writeChunk(destination, formattedID);
3422
return writeChunkAndReturn(destination, placeholder2);
3459
3460
export function writeStartCompletedSuspenseBoundary(
3461
destination: Destination,
3360
- responseState: ResponseState,
3462
+ renderState: RenderState,
3463
): boolean {
3464
return writeChunkAndReturn(destination, startCompletedSuspenseBoundary);
3465
}
3466
export function writeStartPendingSuspenseBoundary(
3467
destination: Destination,
3366
- responseState: ResponseState,
3468
+ renderState: RenderState,
3469
id: SuspenseBoundaryID,
3470
): boolean {
3471
writeChunk(destination, startPendingSuspenseBoundary1);
3481
}
3482
export function writeStartClientRenderedSuspenseBoundary(
3483
destination: Destination,
3382
- responseState: ResponseState,
3484
+ renderState: RenderState,
3485
errorDigest: ?string,
3486
errorMesssage: ?string,
3487
errorComponentStack: ?string,
3532
}
3533
export function writeEndCompletedSuspenseBoundary(
3534
destination: Destination,
3433
- responseState: ResponseState,
3535
+ renderState: RenderState,
3536
): boolean {
3537
return writeChunkAndReturn(destination, endSuspenseBoundary);
3538
}
3539
export function writeEndPendingSuspenseBoundary(
3540
destination: Destination,
3439
- responseState: ResponseState,
3541
+ renderState: RenderState,
3542
): boolean {
3543
return writeChunkAndReturn(destination, endSuspenseBoundary);
3544
}
3545
export function writeEndClientRenderedSuspenseBoundary(
3546
destination: Destination,
3445
- responseState: ResponseState,
3547
+ renderState: RenderState,
3548
): boolean {
3549
return writeChunkAndReturn(destination, endSuspenseBoundary);
3550
}
3587
3588
export function writeStartSegment(
3589
destination: Destination,
3488
- responseState: ResponseState,
3590
+ renderState: RenderState,
3591
formatContext: FormatContext,
3592
id: number,
3593
): boolean {
3596
case HTML_HTML_MODE:
3597
case HTML_MODE: {
3598
writeChunk(destination, startSegmentHTML);
3497
- writeChunk(destination, responseState.segmentPrefix);
3599
+ writeChunk(destination, renderState.segmentPrefix);
3600
writeChunk(destination, stringToChunk(id.toString(16)));
3601
return writeChunkAndReturn(destination, startSegmentHTML2);
3602
}
3603
case SVG_MODE: {
3604
writeChunk(destination, startSegmentSVG);
3503
- writeChunk(destination, responseState.segmentPrefix);
3605
+ writeChunk(destination, renderState.segmentPrefix);
3606
writeChunk(destination, stringToChunk(id.toString(16)));
3607
return writeChunkAndReturn(destination, startSegmentSVG2);
3608
}
3609
case MATHML_MODE: {
3610
writeChunk(destination, startSegmentMathML);
3509
- writeChunk(destination, responseState.segmentPrefix);
3611
+ writeChunk(destination, renderState.segmentPrefix);
3612
writeChunk(destination, stringToChunk(id.toString(16)));
3613
return writeChunkAndReturn(destination, startSegmentMathML2);
3614
}
3615
case HTML_TABLE_MODE: {
3616
writeChunk(destination, startSegmentTable);
3515
- writeChunk(destination, responseState.segmentPrefix);
3617
+ writeChunk(destination, renderState.segmentPrefix);
3618
writeChunk(destination, stringToChunk(id.toString(16)));
3619
return writeChunkAndReturn(destination, startSegmentTable2);
3620
}
3624
// and it's kind of an edge case to suspend in a table. Totally supported though.
3625
case HTML_TABLE_BODY_MODE: {
3626
writeChunk(destination, startSegmentTableBody);
3525
- writeChunk(destination, responseState.segmentPrefix);
3627
+ writeChunk(destination, renderState.segmentPrefix);
3628
writeChunk(destination, stringToChunk(id.toString(16)));
3629
return writeChunkAndReturn(destination, startSegmentTableBody2);
3630
}
3631
case HTML_TABLE_ROW_MODE: {
3632
writeChunk(destination, startSegmentTableRow);
3531
- writeChunk(destination, responseState.segmentPrefix);
3633
+ writeChunk(destination, renderState.segmentPrefix);
3634
writeChunk(destination, stringToChunk(id.toString(16)));
3635
return writeChunkAndReturn(destination, startSegmentTableRow2);
3636
}
3637
case HTML_COLGROUP_MODE: {
3638
writeChunk(destination, startSegmentColGroup);
3537
- writeChunk(destination, responseState.segmentPrefix);
3639
+ writeChunk(destination, renderState.segmentPrefix);
3640
writeChunk(destination, stringToChunk(id.toString(16)));
3641
return writeChunkAndReturn(destination, startSegmentColGroup2);
3642
}
3694
3695
export function writeCompletedSegmentInstruction(
3696
destination: Destination,
3595
- responseState: ResponseState,
3697
+ resumableState: ResumableState,
3698
+ renderState: RenderState,
3699
contentSegmentID: number,
3700
): boolean {
3701
const scriptFormat =
3702
!enableFizzExternalRuntime ||
3600
- responseState.streamingFormat === ScriptStreamingFormat;
3703
+ resumableState.streamingFormat === ScriptStreamingFormat;
3704
if (scriptFormat) {
3602
- writeChunk(destination, responseState.startInlineScript);
3705
+ writeChunk(destination, renderState.startInlineScript);
3706
if (
3604
- (responseState.instructions & SentCompleteSegmentFunction) ===
3707
+ (resumableState.instructions & SentCompleteSegmentFunction) ===
3708
NothingSent
3709
) {
3710
// The first time we write this, we'll need to include the full implementation.
3608
- responseState.instructions |= SentCompleteSegmentFunction;
3711
+ resumableState.instructions |= SentCompleteSegmentFunction;
3712
writeChunk(destination, completeSegmentScript1Full);
3713
} else {
3714
// Future calls can just reuse the same function.
3719
}
3720
3721
// Write function arguments, which are string literals
3619
- writeChunk(destination, responseState.segmentPrefix);
3722
+ writeChunk(destination, renderState.segmentPrefix);
3723
const formattedID = stringToChunk(contentSegmentID.toString(16));
3724
writeChunk(destination, formattedID);
3725
if (scriptFormat) {
3727
} else {
3728
writeChunk(destination, completeSegmentData2);
3729
}
3627
- writeChunk(destination, responseState.placeholderPrefix);
3730
+ writeChunk(destination, renderState.placeholderPrefix);
3731
writeChunk(destination, formattedID);
3732
3733
if (scriptFormat) {
3768
3769
export function writeCompletedBoundaryInstruction(
3770
destination: Destination,
3668
- responseState: ResponseState,
3771
+ resumableState: ResumableState,
3772
+ renderState: RenderState,
3773
boundaryID: SuspenseBoundaryID,
3774
contentSegmentID: number,
3775
boundaryResources: BoundaryResources,
3776
): boolean {
3777
let requiresStyleInsertion;
3778
if (enableFloat) {
3675
- requiresStyleInsertion = responseState.stylesToHoist;
3779
+ requiresStyleInsertion = renderState.stylesToHoist;
3780
// If necessary stylesheets will be flushed with this instruction.
3781
// Any style tags not yet hoisted in the Document will also be hoisted.
3782
// We reset this state since after this instruction executes all styles
3783
// up to this point will have been hoisted
3680
- responseState.stylesToHoist = false;
3784
+ renderState.stylesToHoist = false;
3785
}
3786
const scriptFormat =
3787
!enableFizzExternalRuntime ||
3684
- responseState.streamingFormat === ScriptStreamingFormat;
3788
+ resumableState.streamingFormat === ScriptStreamingFormat;
3789
if (scriptFormat) {
3686
- writeChunk(destination, responseState.startInlineScript);
3790
+ writeChunk(destination, renderState.startInlineScript);
3791
if (enableFloat && requiresStyleInsertion) {
3792
if (
3689
- (responseState.instructions & SentCompleteBoundaryFunction) ===
3793
+ (resumableState.instructions & SentCompleteBoundaryFunction) ===
3794
NothingSent
3795
) {
3692
- responseState.instructions |=
3796
+ resumableState.instructions |=
3797
SentStyleInsertionFunction | SentCompleteBoundaryFunction;
3798
writeChunk(
3799
destination,
3800
clonePrecomputedChunk(completeBoundaryWithStylesScript1FullBoth),
3801
);
3802
} else if (
3699
- (responseState.instructions & SentStyleInsertionFunction) ===
3803
+ (resumableState.instructions & SentStyleInsertionFunction) ===
3804
NothingSent
3805
) {
3702
- responseState.instructions |= SentStyleInsertionFunction;
3806
+ resumableState.instructions |= SentStyleInsertionFunction;
3807
writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
3808
} else {
3809
writeChunk(destination, completeBoundaryWithStylesScript1Partial);
3810
}
3811
} else {
3812
if (
3709
- (responseState.instructions & SentCompleteBoundaryFunction) ===
3813
+ (resumableState.instructions & SentCompleteBoundaryFunction) ===
3814
NothingSent
3815
) {
3712
- responseState.instructions |= SentCompleteBoundaryFunction;
3816
+ resumableState.instructions |= SentCompleteBoundaryFunction;
3817
writeChunk(destination, completeBoundaryScript1Full);
3818
} else {
3819
writeChunk(destination, completeBoundaryScript1Partial);
3841
} else {
3842
writeChunk(destination, completeBoundaryData2);
3843
}
3740
- writeChunk(destination, responseState.segmentPrefix);
3844
+ writeChunk(destination, renderState.segmentPrefix);
3845
writeChunk(destination, formattedContentID);
3846
if (enableFloat && requiresStyleInsertion) {
3847
// Script and data writers must format this differently:
3868
} else {
3869
writeMore = writeChunkAndReturn(destination, completeBoundaryDataEnd);
3870
}
3767
- return writeBootstrap(destination, responseState) && writeMore;
3871
+ return writeBootstrap(destination, resumableState) && writeMore;
3872
}
3873
3874
const clientRenderScript1Full = stringToPrecomputedChunk(
3889
3890
export function writeClientRenderBoundaryInstruction(
3891
destination: Destination,
3788
- responseState: ResponseState,
3892
+ resumableState: ResumableState,
3893
+ renderState: RenderState,
3894
boundaryID: SuspenseBoundaryID,
3895
errorDigest: ?string,
3896
errorMessage?: string,
3898
): boolean {
3899
const scriptFormat =
3900
!enableFizzExternalRuntime ||
3796
- responseState.streamingFormat === ScriptStreamingFormat;
3901
+ resumableState.streamingFormat === ScriptStreamingFormat;
3902
if (scriptFormat) {
3798
- writeChunk(destination, responseState.startInlineScript);
3903
+ writeChunk(destination, renderState.startInlineScript);
3904
if (
3800
- (responseState.instructions & SentClientRenderFunction) ===
3905
+ (resumableState.instructions & SentClientRenderFunction) ===
3906
NothingSent
3907
) {
3908
// The first time we write this, we'll need to include the full implementation.
3804
- responseState.instructions |= SentClientRenderFunction;
3909
+ resumableState.instructions |= SentClientRenderFunction;
3910
writeChunk(destination, clientRenderScript1Full);
3911
} else {
3912
// Future calls can just reuse the same function.
4107
export function writeResourcesForBoundary(
4108
destination: Destination,
4109
boundaryResources: BoundaryResources,
4005
- responseState: ResponseState,
4110
+ renderState: RenderState,
4111
): boolean {
4112
// Reset these on each invocation, they are only safe to read in this function
4113
currentlyRenderingBoundaryHasStylesToHoist = false;
4116
// Flush each Boundary resource
4117
boundaryResources.forEach(flushStyleTagsLateForBoundary, destination);
4118
if (currentlyRenderingBoundaryHasStylesToHoist) {
4014
- responseState.stylesToHoist = true;
4119
+ renderState.stylesToHoist = true;
4120
}
4121
return destinationHasCapacity;
4122
}
4267
// of the flushing code in Fizz.
4268
export function writePreamble(
4269
destination: Destination,
4165
- resources: Resources,
4166
- responseState: ResponseState,
4270
+ resumableState: ResumableState,
4271
+ renderState: RenderState,
4272
willFlushAllSegments: boolean,
4273
): void {
4274
// This function must be called exactly once on every request
4275
if (
4276
enableFizzExternalRuntime &&
4277
!willFlushAllSegments &&
4173
- responseState.externalRuntimeScript
4278
+ resumableState.externalRuntimeScript
4279
) {
4280
// If the root segment is incomplete due to suspended tasks
4281
// (e.g. willFlushAllSegments = false) and we are using data
4282
// streaming format, ensure the external runtime is sent.
4283
// (User code could choose to send this even earlier by calling
4284
// preinit(...), if they know they will suspend).
4180
- const {src, chunks} = responseState.externalRuntimeScript;
4181
- internalPreinitScript(resources, src, chunks);
4285
+ const {src, chunks} = resumableState.externalRuntimeScript;
4286
+ internalPreinitScript(resumableState, src, chunks);
4287
}
4288
4184
- const htmlChunks = responseState.htmlChunks;
4185
- const headChunks = responseState.headChunks;
4289
+ const htmlChunks = renderState.htmlChunks;
4290
+ const headChunks = renderState.headChunks;
4291
4292
let i = 0;
4293
4314
}
4315
4316
// Emit high priority Hoistables
4212
- const charsetChunks = responseState.charsetChunks;
4317
+ const charsetChunks = renderState.charsetChunks;
4318
for (i = 0; i < charsetChunks.length; i++) {
4319
writeChunk(destination, charsetChunks[i]);
4320
}
4321
charsetChunks.length = 0;
4322
4323
// emit preconnect resources
4219
- resources.preconnects.forEach(flushResourceInPreamble, destination);
4220
- resources.preconnects.clear();
4324
+ resumableState.preconnects.forEach(flushResourceInPreamble, destination);
4325
+ resumableState.preconnects.clear();
4326
4222
- const preconnectChunks = responseState.preconnectChunks;
4327
+ const preconnectChunks = renderState.preconnectChunks;
4328
for (i = 0; i < preconnectChunks.length; i++) {
4329
writeChunk(destination, preconnectChunks[i]);
4330
}
4331
preconnectChunks.length = 0;
4332
4228
- resources.fontPreloads.forEach(flushResourceInPreamble, destination);
4229
- resources.fontPreloads.clear();
4333
+ resumableState.fontPreloads.forEach(flushResourceInPreamble, destination);
4334
+ resumableState.fontPreloads.clear();
4335
4231
- resources.highImagePreloads.forEach(flushResourceInPreamble, destination);
4232
- resources.highImagePreloads.clear();
4336
+ resumableState.highImagePreloads.forEach(
4337
+ flushResourceInPreamble,
4338
+ destination,
4339
+ );
4340
+ resumableState.highImagePreloads.clear();
4341
4342
// Flush unblocked stylesheets by precedence
4235
- resources.precedences.forEach(flushAllStylesInPreamble, destination);
4343
+ resumableState.precedences.forEach(flushAllStylesInPreamble, destination);
4344
4237
- resources.bootstrapScripts.forEach(flushResourceInPreamble, destination);
4345
+ resumableState.bootstrapScripts.forEach(flushResourceInPreamble, destination);
4346
4239
- resources.scripts.forEach(flushResourceInPreamble, destination);
4240
- resources.scripts.clear();
4347
+ resumableState.scripts.forEach(flushResourceInPreamble, destination);
4348
+ resumableState.scripts.clear();
4349
4242
- resources.bulkPreloads.forEach(flushResourceInPreamble, destination);
4243
- resources.bulkPreloads.clear();
4350
+ resumableState.bulkPreloads.forEach(flushResourceInPreamble, destination);
4351
+ resumableState.bulkPreloads.clear();
4352
4353
// Write embedding preloadChunks
4246
- const preloadChunks = responseState.preloadChunks;
4354
+ const preloadChunks = renderState.preloadChunks;
4355
for (i = 0; i < preloadChunks.length; i++) {
4356
writeChunk(destination, preloadChunks[i]);
4357
}
4358
preloadChunks.length = 0;
4359
4360
// Write embedding hoistableChunks
4253
- const hoistableChunks = responseState.hoistableChunks;
4361
+ const hoistableChunks = renderState.hoistableChunks;
4362
for (i = 0; i < hoistableChunks.length; i++) {
4363
writeChunk(destination, hoistableChunks[i]);
4364
}
4383
// of the flushing code in Fizz.
4384
export function writeHoistables(
4385
destination: Destination,
4278
- resources: Resources,
4279
- responseState: ResponseState,
4386
+ resumableState: ResumableState,
4387
+ renderState: RenderState,
4388
): void {
4389
let i = 0;
4390
4393
// We omit charsetChunks because we have already sent the shell and if it wasn't
4394
// already sent it is too late now.
4395
4288
- resources.preconnects.forEach(flushResourceLate, destination);
4289
- resources.preconnects.clear();
4396
+ resumableState.preconnects.forEach(flushResourceLate, destination);
4397
+ resumableState.preconnects.clear();
4398
4291
- const preconnectChunks = responseState.preconnectChunks;
4399
+ const preconnectChunks = renderState.preconnectChunks;
4400
for (i = 0; i < preconnectChunks.length; i++) {
4401
writeChunk(destination, preconnectChunks[i]);
4402
}
4403
preconnectChunks.length = 0;
4404
4297
- resources.fontPreloads.forEach(flushResourceLate, destination);
4298
- resources.fontPreloads.clear();
4405
+ resumableState.fontPreloads.forEach(flushResourceLate, destination);
4406
+ resumableState.fontPreloads.clear();
4407
4300
- resources.highImagePreloads.forEach(flushResourceInPreamble, destination);
4301
- resources.highImagePreloads.clear();
4408
+ resumableState.highImagePreloads.forEach(
4409
+ flushResourceInPreamble,
4410
+ destination,
4411
+ );
4412
+ resumableState.highImagePreloads.clear();
4413
4414
// Preload any stylesheets. these will emit in a render instruction that follows this
4415
// but we want to kick off preloading as soon as possible
4305
- resources.precedences.forEach(preloadLateStyles, destination);
4416
+ resumableState.precedences.forEach(preloadLateStyles, destination);
4417
4418
// bootstrap scripts should flush above script priority but these can only flush in the preamble
4419
// so we elide the code here for performance
4420
4310
- resources.scripts.forEach(flushResourceLate, destination);
4311
- resources.scripts.clear();
4421
+ resumableState.scripts.forEach(flushResourceLate, destination);
4422
+ resumableState.scripts.clear();
4423
4313
- resources.bulkPreloads.forEach(flushResourceLate, destination);
4314
- resources.bulkPreloads.clear();
4424
+ resumableState.bulkPreloads.forEach(flushResourceLate, destination);
4425
+ resumableState.bulkPreloads.clear();
4426
4427
// Write embedding preloadChunks
4317
- const preloadChunks = responseState.preloadChunks;
4428
+ const preloadChunks = renderState.preloadChunks;
4429
for (i = 0; i < preloadChunks.length; i++) {
4430
writeChunk(destination, preloadChunks[i]);
4431
}
4432
preloadChunks.length = 0;
4433
4434
// Write embedding hoistableChunks
4324
- const hoistableChunks = responseState.hoistableChunks;
4435
+ const hoistableChunks = renderState.hoistableChunks;
4436
for (i = 0; i < hoistableChunks.length; i++) {
4437
writeChunk(destination, hoistableChunks[i]);
4438
}
4441
4442
export function writePostamble(
4443
destination: Destination,
4333
- responseState: ResponseState,
4444
+ resumableState: ResumableState,
4445
): void {
4335
- if (responseState.hasBody) {
4446
+ if (resumableState.hasBody) {
4447
writeChunk(destination, endTag1);
4448
writeChunk(destination, stringToChunk('body'));
4449
writeChunk(destination, endTag2);
4450
}
4340
- if (responseState.htmlChunks) {
4451
+ if (resumableState.hasHtml) {
4452
writeChunk(destination, endTag1);
4453
writeChunk(destination, stringToChunk('html'));
4454
writeChunk(destination, endTag2);
4934
| PreloadResource
4935
| PreconnectResource;
4936
4826
-export type Resources = {
4827
- // Request local cache
4828
- preloadsMap: Map<string, PreloadResource>,
4829
- preconnectsMap: Map<string, PreconnectResource>,
4830
- stylesMap: Map<string, StyleResource>,
4831
- scriptsMap: Map<string, ScriptResource>,
4832
-
4833
- // Flushing queues for Resource dependencies
4834
- preconnects: Set<PreconnectResource>,
4835
- fontPreloads: Set<PreloadResource>,
4836
- highImagePreloads: Set<PreloadResource>,
4837
- // usedImagePreloads: Set<PreloadResource>,
4838
- precedences: Map<string, Set<StyleResource>>,
4839
- stylePrecedences: Map<string, StyleTagResource>,
4840
- bootstrapScripts: Set<PreloadResource>,
4841
- scripts: Set<ScriptResource>,
4842
- bulkPreloads: Set<PreloadResource>,
4843
-
4844
- // Module-global-like reference for current boundary resources
4845
- boundaryResources: ?BoundaryResources,
4846
- ...
4847
-};
4848
-
4849
-// @TODO add bootstrap script to implicit preloads
4850
-export function createResources(): Resources {
4851
- return {
4852
- // persistent
4853
- preloadsMap: new Map(),
4854
- preconnectsMap: new Map(),
4855
- stylesMap: new Map(),
4856
- scriptsMap: new Map(),
4857
-
4858
- // cleared on flush
4859
- preconnects: new Set(),
4860
- fontPreloads: new Set(),
4861
- highImagePreloads: new Set(),
4862
- // usedImagePreloads: new Set(),
4863
- precedences: new Map(),
4864
- stylePrecedences: new Map(),
4865
- bootstrapScripts: new Set(),
4866
- scripts: new Set(),
4867
- bulkPreloads: new Set(),
4868
-
4869
- // like a module global for currently rendering boundary
4870
- boundaryResources: null,
4871
- };
4872
-}
4873
-
4937
export type BoundaryResources = Set<StyleResource>;
4938
4939
export function createBoundaryResources(): BoundaryResources {
4941
}
4942
4943
export function setCurrentlyRenderingBoundaryResourcesTarget(
4881
- resources: Resources,
4944
+ renderState: RenderState,
4945
boundaryResources: null | BoundaryResources,
4946
) {
4884
- resources.boundaryResources = boundaryResources;
4947
+ renderState.boundaryResources = boundaryResources;
4948
}
4949
4950
function getResourceKey(as: string, href: string): string {
4964
// fetching) and we don't want to warn in those cases.
4965
return;
4966
}
4904
- const resources = getResources(request);
4967
+ const resumableState = getResumableState(request);
4968
if (__DEV__) {
4969
if (typeof href !== 'string' || !href) {
4970
console.error(
4991
4992
if (typeof href === 'string' && href) {
4993
const key = getResourceKey('prefetchDNS', href);
4931
- let resource = resources.preconnectsMap.get(key);
4994
+ let resource = resumableState.preconnectsMap.get(key);
4995
if (!resource) {
4996
resource = {
4997
type: 'preconnect',
4999
state: NoState,
5000
props: null,
5001
};
4939
- resources.preconnectsMap.set(key, resource);
5002
+ resumableState.preconnectsMap.set(key, resource);
5003
pushLinkImpl(
5004
resource.chunks,
5005
({href, rel: 'dns-prefetch'}: PreconnectProps),
5006
);
5007
}
4945
- resources.preconnects.add(resource);
5008
+ resumableState.preconnects.add(resource);
5009
flushResources(request);
5010
}
5011
}
5023
// fetching) and we don't want to warn in those cases.
5024
return;
5025
}
4963
- const resources = getResources(request);
5026
+ const resumableState = getResumableState(request);
5027
if (__DEV__) {
5028
if (typeof href !== 'string' || !href) {
5029
console.error(
5054
const key = `[preconnect][${
5055
crossOrigin === null ? 'null' : crossOrigin
5056
}]${href}`;
4994
- let resource = resources.preconnectsMap.get(key);
5057
+ let resource = resumableState.preconnectsMap.get(key);
5058
if (!resource) {
5059
resource = {
5060
type: 'preconnect',
5062
state: NoState,
5063
props: null,
5064
};
5002
- resources.preconnectsMap.set(key, resource);
5065
+ resumableState.preconnectsMap.set(key, resource);
5066
pushLinkImpl(
5067
resource.chunks,
5068
({rel: 'preconnect', href, crossOrigin}: PreconnectProps),
5069
);
5070
}
5008
- resources.preconnects.add(resource);
5071
+ resumableState.preconnects.add(resource);
5072
flushResources(request);
5073
}
5074
}
5086
// fetching) and we don't want to warn in those cases.
5087
return;
5088
}
5026
- const resources = getResources(request);
5089
+ const resumableState = getResumableState(request);
5090
if (__DEV__) {
5091
let encountered = '';
5092
if (typeof href !== 'string' || !href) {
5129
} else {
5130
key = getResourceKey(as, href);
5131
}
5069
- let resource = resources.preloadsMap.get(key);
5132
+ let resource = resumableState.preloadsMap.get(key);
5133
if (!resource) {
5134
resource = {
5135
type: 'preload',
5137
state: NoState,
5138
props: preloadPropsFromPreloadOptions(href, as, options),
5139
};
5077
- resources.preloadsMap.set(key, resource);
5140
+ resumableState.preloadsMap.set(key, resource);
5141
pushLinkImpl(resource.chunks, resource.props);
5142
}
5143
if (as === 'font') {
5081
- resources.fontPreloads.add(resource);
5144
+ resumableState.fontPreloads.add(resource);
5145
} else if (as === 'image' && options.fetchPriority === 'high') {
5083
- resources.highImagePreloads.add(resource);
5146
+ resumableState.highImagePreloads.add(resource);
5147
} else {
5085
- resources.bulkPreloads.add(resource);
5148
+ resumableState.bulkPreloads.add(resource);
5149
}
5150
flushResources(request);
5151
}
5164
// fetching) and we don't want to warn in those cases.
5165
return;
5166
}
5104
- const resources = getResources(request);
5167
+ const resumableState = getResumableState(request);
5168
if (__DEV__) {
5169
let encountered = '';
5170
if (typeof href !== 'string' || !href) {
5192
const as =
5193
options && typeof options.as === 'string' ? options.as : 'script';
5194
const key = getResourceKey(as, href);
5132
- let resource = resources.preloadsMap.get(key);
5195
+ let resource = resumableState.preloadsMap.get(key);
5196
if (!resource) {
5197
resource = {
5198
type: 'preload',
5200
state: NoState,
5201
props: preloadModulePropsFromPreloadModuleOptions(href, as, options),
5202
};
5140
- resources.preloadsMap.set(key, resource);
5203
+ resumableState.preloadsMap.set(key, resource);
5204
pushLinkImpl(resource.chunks, resource.props);
5205
}
5143
- resources.bulkPreloads.add(resource);
5206
+ resumableState.bulkPreloads.add(resource);
5207
flushResources(request);
5208
}
5209
}
5221
// fetching) and we don't want to warn in those cases.
5222
return;
5223
}
5161
- const resources = getResources(request);
5224
+ const resumableState = getResumableState(request);
5225
if (__DEV__) {
5226
if (typeof href !== 'string' || !href) {
5227
console.error(
5250
switch (as) {
5251
case 'style': {
5252
const key = getResourceKey(as, href);
5190
- let resource = resources.stylesMap.get(key);
5253
+ let resource = resumableState.stylesMap.get(key);
5254
const precedence = options.precedence || 'default';
5255
if (!resource) {
5256
let state = NoState;
5194
- const preloadResource = resources.preloadsMap.get(key);
5257
+ const preloadResource = resumableState.preloadsMap.get(key);
5258
if (preloadResource && preloadResource.state & Flushed) {
5259
state = PreloadFlushed;
5260
}
5264
state,
5265
props: stylesheetPropsFromPreinitOptions(href, precedence, options),
5266
};
5204
- resources.stylesMap.set(key, resource);
5205
- let precedenceSet = resources.precedences.get(precedence);
5267
+ resumableState.stylesMap.set(key, resource);
5268
+ let precedenceSet = resumableState.precedences.get(precedence);
5269
if (!precedenceSet) {
5270
precedenceSet = new Set();
5208
- resources.precedences.set(precedence, precedenceSet);
5271
+ resumableState.precedences.set(precedence, precedenceSet);
5272
const emptyStyleResource = {
5273
type: 'style',
5274
chunks: ([]: Array<Chunk | PrecomputedChunk>),
5280
};
5281
precedenceSet.add(emptyStyleResource);
5282
if (__DEV__) {
5220
- if (resources.stylePrecedences.has(precedence)) {
5283
+ if (resumableState.stylePrecedences.has(precedence)) {
5284
console.error(
5285
'React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.',
5286
precedence,
5287
);
5288
}
5289
}
5227
- resources.stylePrecedences.set(precedence, emptyStyleResource);
5290
+ resumableState.stylePrecedences.set(precedence, emptyStyleResource);
5291
}
5292
precedenceSet.add(resource);
5293
flushResources(request);
5297
case 'script': {
5298
const src = href;
5299
const key = getResourceKey(as, src);
5237
- let resource = resources.scriptsMap.get(key);
5300
+ let resource = resumableState.scriptsMap.get(key);
5301
if (!resource) {
5302
resource = {
5303
type: 'script',
5305
state: NoState,
5306
props: null,
5307
};
5245
- resources.scriptsMap.set(key, resource);
5308
+ resumableState.scriptsMap.set(key, resource);
5309
const resourceProps = scriptPropsFromPreinitOptions(src, options);
5247
- resources.scripts.add(resource);
5310
+ resumableState.scripts.add(resource);
5311
pushScriptImpl(resource.chunks, resourceProps);
5312
flushResources(request);
5313
}
5330
// fetching) and we don't want to warn in those cases.
5331
return;
5332
}
5270
- const resources = getResources(request);
5333
+ const resumableState = getResumableState(request);
5334
if (__DEV__) {
5335
let encountered = '';
5336
if (typeof href !== 'string' || !href) {
5382
case 'script': {
5383
const src = href;
5384
const key = getResourceKey(as, src);
5322
- let resource = resources.scriptsMap.get(key);
5385
+ let resource = resumableState.scriptsMap.get(key);
5386
if (!resource) {
5387
resource = {
5388
type: 'script',
5390
state: NoState,
5391
props: null,
5392
};
5330
- resources.scriptsMap.set(key, resource);
5393
+ resumableState.scriptsMap.set(key, resource);
5394
const resourceProps = modulePropsFromPreinitModuleOptions(
5395
src,
5396
options,
5397
);
5335
- resources.scripts.add(resource);
5398
+ resumableState.scripts.add(resource);
5399
pushScriptImpl(resource.chunks, resourceProps);
5400
flushResources(request);
5401
}
5410
// scripts at any other point in time we will need to check whether the preload
5411
// already exists and not assume it
5412
function preloadBootstrapScript(
5350
- resources: Resources,
5413
+ resumableState: ResumableState,
5414
src: string,
5415
nonce: ?string,
5416
integrity: ?string,
5418
): void {
5419
const key = getResourceKey('script', src);
5420
if (__DEV__) {
5358
- if (resources.preloadsMap.has(key)) {
5421
+ if (resumableState.preloadsMap.has(key)) {
5422
// This is coded as a React error because it should be impossible for a userspace preload to preempt this call
5423
// If a userspace preload can preempt it then this assumption is broken and we need to reconsider this strategy
5424
// rather than instruct the user to not preload their bootstrap scripts themselves
5443
state: NoState,
5444
props,
5445
};
5383
- resources.preloadsMap.set(key, resource);
5384
- resources.bootstrapScripts.add(resource);
5446
+ resumableState.preloadsMap.set(key, resource);
5447
+ resumableState.bootstrapScripts.add(resource);
5448
pushLinkImpl(resource.chunks, props);
5449
}
5450
5453
// scripts at any other point in time we will need to check whether the preload
5454
// already exists and not assume it
5455
function preloadBootstrapModule(
5393
- resources: Resources,
5456
+ resumableState: ResumableState,
5457
src: string,
5458
nonce: ?string,
5459
integrity: ?string,
5461
): void {
5462
const key = getResourceKey('script', src);
5463
if (__DEV__) {
5401
- if (resources.preloadsMap.has(key)) {
5464
+ if (resumableState.preloadsMap.has(key)) {
5465
// This is coded as a React error because it should be impossible for a userspace preload to preempt this call
5466
// If a userspace preload can preempt it then this assumption is broken and we need to reconsider this strategy
5467
// rather than instruct the user to not preload their bootstrap scripts themselves
5485
state: NoState,
5486
props,
5487
};
5425
- resources.preloadsMap.set(key, resource);
5426
- resources.bootstrapScripts.add(resource);
5488
+ resumableState.preloadsMap.set(key, resource);
5489
+ resumableState.bootstrapScripts.add(resource);
5490
pushLinkImpl(resource.chunks, props);
5491
return;
5492
}
5493
5494
function internalPreinitScript(
5432
- resources: Resources,
5495
+ resumableState: ResumableState,
5496
src: string,
5497
chunks: Array<Chunk | PrecomputedChunk>,
5498
): void {
5499
const key = getResourceKey('script', src);
5437
- let resource = resources.scriptsMap.get(key);
5500
+ let resource = resumableState.scriptsMap.get(key);
5501
if (!resource) {
5502
resource = {
5503
type: 'script',
5505
state: NoState,
5506
props: null,
5507
};
5445
- resources.scriptsMap.set(key, resource);
5446
- resources.scripts.add(resource);
5508
+ resumableState.scriptsMap.set(key, resource);
5509
+ resumableState.scripts.add(resource);
5510
}
5511
return;
5512
}
5638
}
5639
5640
export function hoistResources(
5578
- resources: Resources,
5641
+ renderState: RenderState,
5642
source: BoundaryResources,
5643
): void {
5581
- const currentBoundaryResources = resources.boundaryResources;
5644
+ const currentBoundaryResources = renderState.boundaryResources;
5645
if (currentBoundaryResources) {
5646
source.forEach(hoistStyleResource, currentBoundaryResources);
5647
}