mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Adds a new option to `react-dom/server` entrypoints.
`onHeaders: (headers: Headers) => void` (non node envs)
`onHeaders: (headers: { Link?: string }) => void` (node envs)
When any `renderTo...` or `prerender...` function is called and this
option is provided the supplied function will be called sometime on or
before completion of the render with some preload link headers.
When provided during a `renderTo...` the callback will usually be called
after the first pass at work. The idea here is we want to get a set of
headers to start the browser loading well before the shell is ready. We
don't wait for the shell because if we did we may as well send the
preloads as tags in the HTML.
When provided during a `prerender...` the callback will be called after
the entire prerender is complete. The idea here is we are not responding
to a live request and it is preferable to capture as much as possible
for preloading as Headers in case the prerender was unable to finish the
shell.
Currently the following resources are always preloaded as headers when
the option is provided
1. prefetchDNS and preconnects
2. font preloads
3. high priority image preloads
Additionally if we are providing headers when the shell is incomplete
(regardless of whether it is render or prerender) we will also include
any stylesheet Resources (ones with a precedence prop)
There is a second option `maxHeadersLength?: number` which allows you to
specify the maximum length of the header content in unicode code units.
This is what you get when you read the length property of a string in
javascript. It's improtant to note that this is not the same as the
utf-8 byte length when these headers are serialized in a Response. The
utf8 representation may be the same size, or larger but it will never be
smaller.
If you do not supply a `maxHeadersLength` we defaul to `2000`. This was
chosen as half the value of the max headers length supported by commonly
known web servers and CDNs. many browser and web server can support
significantly more headers than this so you can use this option to
increase the headers limit. You can also of course use it to be even
more conservative. Again it is important to keep in mind there is no
direct translation between the max length and the bytelength and so if
you want to stay under a certain byte length you need to be potentially
more aggressive in the maxHeadersLength you choose.
Conceptually `onHeaders` could be called more than once as new headers
are discovered however if we haven't started flushing yet but since most
APIs for the server including the web standard Response only allow you
to set headers once the current implementation will only call it one
time
266 lines
7.9 KiB
JavaScript
266 lines
7.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {
|
|
RenderState as BaseRenderState,
|
|
ResumableState,
|
|
BoundaryResources,
|
|
StyleQueue,
|
|
Resource,
|
|
HeadersDescriptor,
|
|
} from './ReactFizzConfigDOM';
|
|
|
|
import {
|
|
createRenderState as createRenderStateImpl,
|
|
pushTextInstance as pushTextInstanceImpl,
|
|
pushSegmentFinale as pushSegmentFinaleImpl,
|
|
writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl,
|
|
writeStartClientRenderedSuspenseBoundary as writeStartClientRenderedSuspenseBoundaryImpl,
|
|
writeEndCompletedSuspenseBoundary as writeEndCompletedSuspenseBoundaryImpl,
|
|
writeEndClientRenderedSuspenseBoundary as writeEndClientRenderedSuspenseBoundaryImpl,
|
|
} from './ReactFizzConfigDOM';
|
|
|
|
import type {
|
|
Destination,
|
|
Chunk,
|
|
PrecomputedChunk,
|
|
} from 'react-server/src/ReactServerStreamConfig';
|
|
|
|
import type {FormStatus} from '../shared/ReactDOMFormActions';
|
|
|
|
import {NotPending} from '../shared/ReactDOMFormActions';
|
|
|
|
export const isPrimaryRenderer = false;
|
|
|
|
export type RenderState = {
|
|
// Keep this in sync with ReactFizzConfigDOM
|
|
placeholderPrefix: PrecomputedChunk,
|
|
segmentPrefix: PrecomputedChunk,
|
|
boundaryPrefix: PrecomputedChunk,
|
|
startInlineScript: PrecomputedChunk,
|
|
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
|
|
headChunks: null | Array<Chunk | PrecomputedChunk>,
|
|
externalRuntimeScript: null | any,
|
|
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
|
|
onHeaders: void | ((headers: HeadersDescriptor) => void),
|
|
headers: null | {
|
|
preconnects: string,
|
|
fontPreloads: string,
|
|
highImagePreloads: string,
|
|
remainingCapacity: number,
|
|
},
|
|
resets: BaseRenderState['resets'],
|
|
charsetChunks: Array<Chunk | PrecomputedChunk>,
|
|
preconnectChunks: Array<Chunk | PrecomputedChunk>,
|
|
importMapChunks: Array<Chunk | PrecomputedChunk>,
|
|
preloadChunks: Array<Chunk | PrecomputedChunk>,
|
|
hoistableChunks: Array<Chunk | PrecomputedChunk>,
|
|
preconnects: Set<Resource>,
|
|
fontPreloads: Set<Resource>,
|
|
highImagePreloads: Set<Resource>,
|
|
// usedImagePreloads: Set<Resource>,
|
|
styles: Map<string, StyleQueue>,
|
|
bootstrapScripts: Set<Resource>,
|
|
scripts: Set<Resource>,
|
|
bulkPreloads: Set<Resource>,
|
|
preloads: {
|
|
images: Map<string, Resource>,
|
|
stylesheets: Map<string, Resource>,
|
|
scripts: Map<string, Resource>,
|
|
moduleScripts: Map<string, Resource>,
|
|
},
|
|
boundaryResources: ?BoundaryResources,
|
|
stylesToHoist: boolean,
|
|
// This is an extra field for the legacy renderer
|
|
generateStaticMarkup: boolean,
|
|
};
|
|
|
|
export function createRenderState(
|
|
resumableState: ResumableState,
|
|
generateStaticMarkup: boolean,
|
|
): RenderState {
|
|
const renderState = createRenderStateImpl(
|
|
resumableState,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
);
|
|
return {
|
|
// Keep this in sync with ReactFizzConfigDOM
|
|
placeholderPrefix: renderState.placeholderPrefix,
|
|
segmentPrefix: renderState.segmentPrefix,
|
|
boundaryPrefix: renderState.boundaryPrefix,
|
|
startInlineScript: renderState.startInlineScript,
|
|
htmlChunks: renderState.htmlChunks,
|
|
headChunks: renderState.headChunks,
|
|
externalRuntimeScript: renderState.externalRuntimeScript,
|
|
bootstrapChunks: renderState.bootstrapChunks,
|
|
onHeaders: renderState.onHeaders,
|
|
headers: renderState.headers,
|
|
resets: renderState.resets,
|
|
charsetChunks: renderState.charsetChunks,
|
|
preconnectChunks: renderState.preconnectChunks,
|
|
importMapChunks: renderState.importMapChunks,
|
|
preloadChunks: renderState.preloadChunks,
|
|
hoistableChunks: renderState.hoistableChunks,
|
|
preconnects: renderState.preconnects,
|
|
fontPreloads: renderState.fontPreloads,
|
|
highImagePreloads: renderState.highImagePreloads,
|
|
// usedImagePreloads: renderState.usedImagePreloads,
|
|
styles: renderState.styles,
|
|
bootstrapScripts: renderState.bootstrapScripts,
|
|
scripts: renderState.scripts,
|
|
bulkPreloads: renderState.bulkPreloads,
|
|
preloads: renderState.preloads,
|
|
boundaryResources: renderState.boundaryResources,
|
|
stylesToHoist: renderState.stylesToHoist,
|
|
|
|
// This is an extra field for the legacy renderer
|
|
generateStaticMarkup,
|
|
};
|
|
}
|
|
|
|
import {
|
|
stringToChunk,
|
|
stringToPrecomputedChunk,
|
|
} from 'react-server/src/ReactServerStreamConfig';
|
|
|
|
// this chunk is empty on purpose because we do not want to emit the DOCTYPE in legacy mode
|
|
export const doctypeChunk: PrecomputedChunk = stringToPrecomputedChunk('');
|
|
|
|
export type {
|
|
ResumableState,
|
|
BoundaryResources,
|
|
FormatContext,
|
|
} from './ReactFizzConfigDOM';
|
|
|
|
export {
|
|
getChildFormatContext,
|
|
makeId,
|
|
pushStartInstance,
|
|
pushEndInstance,
|
|
pushStartCompletedSuspenseBoundary,
|
|
pushEndCompletedSuspenseBoundary,
|
|
pushFormStateMarkerIsMatching,
|
|
pushFormStateMarkerIsNotMatching,
|
|
writeStartSegment,
|
|
writeEndSegment,
|
|
writeCompletedSegmentInstruction,
|
|
writeCompletedBoundaryInstruction,
|
|
writeClientRenderBoundaryInstruction,
|
|
writeStartPendingSuspenseBoundary,
|
|
writeEndPendingSuspenseBoundary,
|
|
writeResourcesForBoundary,
|
|
writePlaceholder,
|
|
writeCompletedRoot,
|
|
createRootFormatContext,
|
|
createResumableState,
|
|
createBoundaryResources,
|
|
writePreamble,
|
|
writeHoistables,
|
|
writePostamble,
|
|
hoistResources,
|
|
setCurrentlyRenderingBoundaryResourcesTarget,
|
|
prepareHostDispatcher,
|
|
resetResumableState,
|
|
emitEarlyPreloads,
|
|
} from './ReactFizzConfigDOM';
|
|
|
|
import escapeTextForBrowser from './escapeTextForBrowser';
|
|
|
|
export function pushTextInstance(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
text: string,
|
|
renderState: RenderState,
|
|
textEmbedded: boolean,
|
|
): boolean {
|
|
if (renderState.generateStaticMarkup) {
|
|
target.push(stringToChunk(escapeTextForBrowser(text)));
|
|
return false;
|
|
} else {
|
|
return pushTextInstanceImpl(target, text, renderState, textEmbedded);
|
|
}
|
|
}
|
|
|
|
export function pushSegmentFinale(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
renderState: RenderState,
|
|
lastPushedText: boolean,
|
|
textEmbedded: boolean,
|
|
): void {
|
|
if (renderState.generateStaticMarkup) {
|
|
return;
|
|
} else {
|
|
return pushSegmentFinaleImpl(
|
|
target,
|
|
renderState,
|
|
lastPushedText,
|
|
textEmbedded,
|
|
);
|
|
}
|
|
}
|
|
|
|
export function writeStartCompletedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
): boolean {
|
|
if (renderState.generateStaticMarkup) {
|
|
// A completed boundary is done and doesn't need a representation in the HTML
|
|
// if we're not going to be hydrating it.
|
|
return true;
|
|
}
|
|
return writeStartCompletedSuspenseBoundaryImpl(destination, renderState);
|
|
}
|
|
export function writeStartClientRenderedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
// flushing these error arguments are not currently supported in this legacy streaming format.
|
|
errorDigest: ?string,
|
|
errorMessage: ?string,
|
|
errorComponentStack: ?string,
|
|
): boolean {
|
|
if (renderState.generateStaticMarkup) {
|
|
// A client rendered boundary is done and doesn't need a representation in the HTML
|
|
// since we'll never hydrate it. This is arguably an error in static generation.
|
|
return true;
|
|
}
|
|
return writeStartClientRenderedSuspenseBoundaryImpl(
|
|
destination,
|
|
renderState,
|
|
errorDigest,
|
|
errorMessage,
|
|
errorComponentStack,
|
|
);
|
|
}
|
|
export function writeEndCompletedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
): boolean {
|
|
if (renderState.generateStaticMarkup) {
|
|
return true;
|
|
}
|
|
return writeEndCompletedSuspenseBoundaryImpl(destination, renderState);
|
|
}
|
|
export function writeEndClientRenderedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
): boolean {
|
|
if (renderState.generateStaticMarkup) {
|
|
return true;
|
|
}
|
|
return writeEndClientRenderedSuspenseBoundaryImpl(destination, renderState);
|
|
}
|
|
|
|
export type TransitionStatus = FormStatus;
|
|
export const NotPendingTransition: TransitionStatus = NotPending;
|