diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index bcc4ad72a8b34..3381005cf5c23 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -116,6 +116,10 @@ describe('ReactDOMFizzServer', () => { // We assume this is a React added ID that's a non-visual implementation detail. continue; } + if (attributes[i].name === 'data-reactroot') { + // We ignore React injected attributes. + continue; + } props[attributes[i].name] = attributes[i].value; } props.children = getVisibleChildren(node); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js index a61727d7fdf04..f805854841ffa 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js @@ -55,7 +55,9 @@ describe('ReactDOMFizzServer', () => {
hello world
, ); const result = await readResult(stream); - expect(result).toMatchInlineSnapshot(`"
hello world
"`); + expect(result).toMatchInlineSnapshot( + `"
hello world
"`, + ); }); // @gate experimental @@ -94,7 +96,7 @@ describe('ReactDOMFizzServer', () => { const result = await readResult(stream); expect(result).toMatchInlineSnapshot( - `"
Done
"`, + `"
Done
"`, ); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js index dcef8c2406a6c..cd93ac5a5163c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js @@ -66,7 +66,7 @@ describe('ReactDOMFizzServer', () => { startWriting(); jest.runAllTimers(); expect(output.result).toMatchInlineSnapshot( - `"
hello world
"`, + `"
hello world
"`, ); }); @@ -84,7 +84,7 @@ describe('ReactDOMFizzServer', () => { // Then React starts writing. startWriting(); expect(output.result).toMatchInlineSnapshot( - `"test
hello world
"`, + `"test
hello world
"`, ); }); @@ -132,7 +132,7 @@ describe('ReactDOMFizzServer', () => { // Then React starts writing. startWriting(); expect(output.result).toMatchInlineSnapshot( - `"test
Done
"`, + `"test
Done
"`, ); }); diff --git a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js index acc6e86879c81..62d27c5e99174 100644 --- a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js +++ b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js @@ -32,6 +32,7 @@ import { OVERLOADED_BOOLEAN, NUMERIC, POSITIVE_NUMERIC, + ROOT_ATTRIBUTE_NAME, } from '../shared/DOMProperty'; import {isUnitlessNumber} from '../shared/CSSProperty'; @@ -63,6 +64,7 @@ export type ResponseState = { sentCompleteSegmentFunction: boolean, sentCompleteBoundaryFunction: boolean, sentClientRenderFunction: boolean, + hasEmittedRoot: boolean, }; // Allows us to keep track of what we've already written so we can refer back to it. @@ -79,6 +81,7 @@ export function createResponseState( sentCompleteSegmentFunction: false, sentCompleteBoundaryFunction: false, sentClientRenderFunction: false, + hasEmittedRoot: false, }; } @@ -99,7 +102,7 @@ type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; // Lets us keep track of contextual state and pick it back up after suspending. export type FormatContext = { - insertionMode: InsertionMode, // root/svg/html/mathml/table + insertionMode: InsertionMode, // svg/html/mathml/table selectedValue: null | string | Array, // the selected value(s) inside a }; @@ -508,6 +511,19 @@ const endOfStartTagSelfClosing = stringToPrecomputedChunk('/>'); const idAttr = stringToPrecomputedChunk(' id="'); const attrEnd = stringToPrecomputedChunk('"'); +const reactRootAttribute = stringToPrecomputedChunk( + ' ' + ROOT_ATTRIBUTE_NAME + '=""', +); +function pushReactRoot( + target: Array, + responseState: ResponseState, +): void { + if (!responseState.hasEmittedRoot) { + responseState.hasEmittedRoot = true; + target.push(reactRootAttribute); + } +} + function pushID( target: Array, responseState: ResponseState, @@ -639,6 +655,7 @@ function pushStartSelect( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag); pushInnerHTML(target, innerHTML, children); @@ -752,6 +769,7 @@ function pushStartOption( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag); return children; @@ -839,6 +857,7 @@ function pushInput( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTagSelfClosing); return null; @@ -903,6 +922,7 @@ function pushStartTextArea( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag); @@ -979,6 +999,7 @@ function pushSelfClosing( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTagSelfClosing); return null; @@ -1015,6 +1036,7 @@ function pushStartMenuItem( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag); return null; @@ -1053,6 +1075,7 @@ function pushStartGenericElement( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag); pushInnerHTML(target, innerHTML, children); @@ -1111,6 +1134,7 @@ function pushStartCustomElement( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag); pushInnerHTML(target, innerHTML, children); @@ -1152,6 +1176,7 @@ function pushStartPreformattedElement( if (assignID !== null) { pushID(target, responseState, assignID, props.id); } + pushReactRoot(target, responseState); target.push(endOfStartTag);