Skip to content

Commit 252fb3b

Browse files
committed
fix duplicate closing tags on non-void resources
1 parent 9341775 commit 252fb3b

File tree

7 files changed

+58
-11
lines changed

7 files changed

+58
-11
lines changed

packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,11 @@ function encodeHTMLTextNode(text: string): string {
374374
return escapeTextForBrowser(text);
375375
}
376376

377+
// This const is returned when a push is fully consumed as a Resource. The runtime
378+
// well compare it against the returned children and avoid pushing the end tag
379+
opaque type ResourceSentinel = mixed;
380+
export const RESOURCE_SENTINAL: ResourceSentinel = {};
381+
377382
const textSeparator = stringToPrecomputedChunk('<!-- -->');
378383

379384
export function pushTextInstance(
@@ -1155,7 +1160,7 @@ function pushMeta(
11551160
props: Object,
11561161
responseState: ResponseState,
11571162
textEmbedded: boolean,
1158-
): ReactNodeList {
1163+
): ReactNodeList | ResourceSentinel {
11591164
if (enableFloat && resourcesFromElement('meta', props)) {
11601165
if (textEmbedded) {
11611166
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
@@ -1164,7 +1169,7 @@ function pushMeta(
11641169
}
11651170
// We have converted this link exclusively to a resource and no longer
11661171
// need to emit it
1167-
return null;
1172+
return RESOURCE_SENTINAL;
11681173
}
11691174

11701175
return pushSelfClosing(target, props, 'meta', responseState);
@@ -1175,7 +1180,7 @@ function pushLink(
11751180
props: Object,
11761181
responseState: ResponseState,
11771182
textEmbedded: boolean,
1178-
): ReactNodeList {
1183+
): ReactNodeList | ResourceSentinel {
11791184
if (enableFloat && resourcesFromLink(props)) {
11801185
if (textEmbedded) {
11811186
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
@@ -1184,7 +1189,7 @@ function pushLink(
11841189
}
11851190
// We have converted this link exclusively to a resource and no longer
11861191
// need to emit it
1187-
return null;
1192+
return RESOURCE_SENTINAL;
11881193
}
11891194

11901195
return pushLinkImpl(target, props, responseState);
@@ -1298,11 +1303,11 @@ function pushStartTitle(
12981303
target: Array<Chunk | PrecomputedChunk>,
12991304
props: Object,
13001305
responseState: ResponseState,
1301-
): ReactNodeList {
1306+
): ReactNodeList | ResourceSentinel {
13021307
if (enableFloat && resourcesFromElement('title', props)) {
13031308
// We have converted this link exclusively to a resource and no longer
13041309
// need to emit it
1305-
return null;
1310+
return RESOURCE_SENTINAL;
13061311
}
13071312

13081313
return pushStartTitleImpl(target, props, responseState);
@@ -1415,7 +1420,7 @@ function pushStartScript(
14151420
props: Object,
14161421
responseState: ResponseState,
14171422
textEmbedded: boolean,
1418-
): ReactNodeList {
1423+
): ReactNodeList | ResourceSentinel {
14191424
if (enableFloat && resourcesFromScript(props)) {
14201425
if (textEmbedded) {
14211426
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
@@ -1424,7 +1429,7 @@ function pushStartScript(
14241429
}
14251430
// We have converted this link exclusively to a resource and no longer
14261431
// need to emit it
1427-
return null;
1432+
return RESOURCE_SENTINAL;
14281433
}
14291434

14301435
return pushStartGenericElement(target, props, 'script', responseState);
@@ -1652,7 +1657,7 @@ export function pushStartInstance(
16521657
responseState: ResponseState,
16531658
formatContext: FormatContext,
16541659
textEmbedded: boolean,
1655-
): ReactNodeList {
1660+
): ReactNodeList | ResourceSentinel {
16561661
if (__DEV__) {
16571662
validateARIAProperties(type, props);
16581663
validateInputProperties(type, props);
@@ -1767,6 +1772,10 @@ export function pushStartInstance(
17671772
}
17681773
}
17691774

1775+
// function pushEndTitle(target: Array<Chunk | PrecomputedChunk>): void {
1776+
// if (enableFloat)
1777+
// }
1778+
17701779
const endTag1 = stringToPrecomputedChunk('</');
17711780
const endTag2 = stringToPrecomputedChunk('>');
17721781

packages/react-dom-bindings/src/server/ReactDOMServerLegacyFormatConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export {
8787
UNINITIALIZED_SUSPENSE_BOUNDARY_ID,
8888
assignSuspenseBoundaryID,
8989
makeId,
90+
RESOURCE_SENTINAL,
9091
pushStartInstance,
9192
pushEndInstance,
9293
pushStartCompletedSuspenseBoundary,

packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ describe('ReactDOMFizzServerBrowser', () => {
499499

500500
const result = await readResult(stream);
501501
expect(result).toEqual(
502-
'<!DOCTYPE html><html><head><title>foo</title></title></head><body>bar</body></html>',
502+
'<!DOCTYPE html><html><head><title>foo</title></head><body>bar</body></html>',
503503
);
504504
});
505505
});

packages/react-dom/src/__tests__/ReactDOMFloat-test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,32 @@ describe('ReactDOMFloat', () => {
382382
);
383383
});
384384

385+
// @gate enableFloat
386+
it('does not emit closing tags in out of order position when rendering a non-void resource type', async () => {
387+
const chunks = [];
388+
389+
writable.on('data', chunk => {
390+
chunks.push(chunk);
391+
});
392+
393+
await actIntoEmptyDocument(() => {
394+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
395+
<>
396+
<title>foo</title>
397+
<html>
398+
<body>bar</body>
399+
</html>
400+
<script async={true} src="foo" />
401+
</>,
402+
);
403+
pipe(writable);
404+
});
405+
expect(chunks).toEqual([
406+
'<!DOCTYPE html><html><script async="" src="foo"></script><title>foo</title><body>bar',
407+
'</body></html>',
408+
]);
409+
});
410+
385411
describe('HostResource', () => {
386412
// @gate enableFloat
387413
it('warns when you update props to an invalid type', async () => {

packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ export function makeId(
121121

122122
const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');
123123

124+
export const RESOURCE_SENTINAL: mixed = {};
125+
124126
export function pushTextInstance(
125127
target: Array<Chunk | PrecomputedChunk>,
126128
text: string,

packages/react-server/src/ReactFizzServer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
setCurrentlyRenderingBoundaryResourcesTarget,
7575
createResources,
7676
createBoundaryResources,
77+
RESOURCE_SENTINAL,
7778
} from './ReactServerFormatConfig';
7879
import {
7980
constructClassInstance,
@@ -694,7 +695,7 @@ function renderHostElement(
694695
pushBuiltInComponentStackInDEV(task, type);
695696
const segment = task.blockedSegment;
696697

697-
const children = pushStartInstance(
698+
const childrenOrResource = pushStartInstance(
698699
segment.chunks,
699700
request.preamble,
700701
type,
@@ -703,6 +704,13 @@ function renderHostElement(
703704
segment.formatContext,
704705
segment.lastPushedText,
705706
);
707+
if (enableFloat && childrenOrResource === RESOURCE_SENTINAL) {
708+
// this push did not actually write to segment chunks because the element
709+
// was a Resource. We pop the stack and return early.
710+
popComponentStackInDEV(task);
711+
return;
712+
}
713+
const children: ReactNodeList = (childrenOrResource: any);
706714
segment.lastPushedText = false;
707715
const prevContext = segment.formatContext;
708716
segment.formatContext = getChildFormatContext(prevContext, type, props);

packages/react-server/src/forks/ReactServerFormatConfig.custom.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,4 @@ export const createResources = $$$hostConfig.createResources;
8282
export const createBoundaryResources = $$$hostConfig.createBoundaryResources;
8383
export const setCurrentlyRenderingBoundaryResourcesTarget =
8484
$$$hostConfig.setCurrentlyRenderingBoundaryResourcesTarget;
85+
export const RESOURCE_SENTINAL = $$$hostConfig.RESOURCE_SENTINAL;

0 commit comments

Comments
 (0)