Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fizz Browser: fix precomputed chunk being cleared on Node 18 #25645

Merged
merged 9 commits into from
Nov 22, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
return content;
}

export function clonePrecomputedChunk(
chunk: PrecomputedChunk,
): PrecomputedChunk {
return chunk;
}

export function closeWithError(destination: Destination, error: mixed): void {
// $FlowFixMe: This is an Error object or the destination accepts other types.
destination.destroy(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
writeChunkAndReturn,
stringToChunk,
stringToPrecomputedChunk,
clonePrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';

import {
Expand Down Expand Up @@ -2443,7 +2444,10 @@ export function writeCompletedBoundaryInstruction(
if (!responseState.sentCompleteBoundaryFunction) {
responseState.sentCompleteBoundaryFunction = true;
responseState.sentStyleInsertionFunction = true;
writeChunk(destination, completeBoundaryWithStylesScript1FullBoth);
writeChunk(
destination,
clonePrecomputedChunk(completeBoundaryWithStylesScript1FullBoth),
);
} else if (!responseState.sentStyleInsertionFunction) {
responseState.sentStyleInsertionFunction = true;
writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
Expand Down
3 changes: 3 additions & 0 deletions packages/react-noop-renderer/src/ReactNoopFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const ReactNoopFlightServer = ReactFlightServer({
stringToPrecomputedChunk(content: string): string {
return content;
},
clonePrecomputedChunk(chunk: string): string {
return chunk;
},
isModuleReference(reference: Object): boolean {
return reference.$$typeof === Symbol.for('react.module.reference');
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
return content;
}

export function clonePrecomputedChunk(
chunk: PrecomputedChunk,
): PrecomputedChunk {
return chunk;
}

export function closeWithError(destination: Destination, error: mixed): void {
destination.done = true;
destination.fatal = true;
Expand Down
27 changes: 26 additions & 1 deletion packages/react-server/src/ReactServerStreamConfigBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ export function writeChunk(
}

if (chunk.length > VIEW_SIZE) {
if (__DEV__) {
if (precomputedChunkSet.has(chunk)) {
console.error(
'A large precomputed chunk was passed to writeChunk without being copied.' +
' Large chunks get enqueued directly and are not copied. This is incompatible with precomputed chunks because you cannot enqueue the same precomputed chunk twice.' +
' Use "cloneChunk" to make a copy of this large precomputed chunk before writing it. This is a bug in React.',
);
}
}
// this chunk may overflow a single view which implies it was not
// one that is cached by the streaming renderer. We will enqueu
// it directly and expect it is not re-used
Expand Down Expand Up @@ -117,8 +126,24 @@ export function stringToChunk(content: string): Chunk {
return textEncoder.encode(content);
}

const precomputedChunkSet: Set<Chunk> = __DEV__ ? new Set() : (null: any);

export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
return textEncoder.encode(content);
const precomputedChunk = textEncoder.encode(content);

if (__DEV__) {
precomputedChunkSet.add(precomputedChunk);
}

return precomputedChunk;
}

export function clonePrecomputedChunk(
precomputedChunk: PrecomputedChunk,
): PrecomputedChunk {
return precomputedChunk.length > VIEW_SIZE
? precomputedChunk.slice()
: precomputedChunk;
}

export function closeWithError(destination: Destination, error: mixed): void {
Expand Down
6 changes: 6 additions & 0 deletions packages/react-server/src/ReactServerStreamConfigBun.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
return content;
}

export function clonePrecomputedChunk(
chunk: PrecomputedChunk,
): PrecomputedChunk {
return chunk;
}

export function closeWithError(destination: Destination, error: mixed): void {
// $FlowFixMe[method-unbinding]
if (typeof destination.error === 'function') {
Expand Down
29 changes: 28 additions & 1 deletion packages/react-server/src/ReactServerStreamConfigNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ function writeViewChunk(destination: Destination, chunk: PrecomputedChunk) {
return;
}
if (chunk.byteLength > VIEW_SIZE) {
if (__DEV__) {
if (precomputedChunkSet && precomputedChunkSet.has(chunk)) {
console.error(
'A large precomputed chunk was passed to writeChunk without being copied.' +
' Large chunks get enqueued directly and are not copied. This is incompatible with precomputed chunks because you cannot enqueue the same precomputed chunk twice.' +
' Use "cloneChunk" to make a copy of this large precomputed chunk before writing it. This is a bug in React.',
);
}
}
// this chunk may overflow a single view which implies it was not
// one that is cached by the streaming renderer. We will enqueu
// it directly and expect it is not re-used
Expand Down Expand Up @@ -185,8 +194,26 @@ export function stringToChunk(content: string): Chunk {
return content;
}

const precomputedChunkSet = __DEV__ ? new Set() : null;

export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
return textEncoder.encode(content);
const precomputedChunk = textEncoder.encode(content);

if (__DEV__) {
if (precomputedChunkSet) {
precomputedChunkSet.add(precomputedChunk);
}
}

return precomputedChunk;
}

export function clonePrecomputedChunk(
precomputedChunk: PrecomputedChunk,
): PrecomputedChunk {
return precomputedChunk.length > VIEW_SIZE
? precomputedChunk.slice()
: precomputedChunk;
}

export function closeWithError(destination: Destination, error: mixed): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ export const close = $$$hostConfig.close;
export const closeWithError = $$$hostConfig.closeWithError;
export const stringToChunk = $$$hostConfig.stringToChunk;
export const stringToPrecomputedChunk = $$$hostConfig.stringToPrecomputedChunk;
export const clonePrecomputedChunk = $$$hostConfig.clonePrecomputedChunk;