diff --git a/src/renderers/dom/fiber/ReactDOMFiberEntry.js b/src/renderers/dom/fiber/ReactDOMFiberEntry.js index bb92698619..3a79c1f50c 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberEntry.js +++ b/src/renderers/dom/fiber/ReactDOMFiberEntry.js @@ -118,7 +118,7 @@ function shouldReuseContent(container) { const rootElement = getReactRootElementInContainer(container); return !!(rootElement && rootElement.nodeType === ELEMENT_NODE && - rootElement.getAttribute(ID_ATTRIBUTE_NAME)); + rootElement.hasAttribute(ID_ATTRIBUTE_NAME)); } function shouldAutoFocusHostComponent(type: string, props: Props): boolean { @@ -392,7 +392,14 @@ var DOMRenderer = ReactFiberReconciler({ return instance.nodeType === 1 && type === instance.nodeName.toLowerCase(); }, - canHydrateTextInstance(instance: Instance | TextInstance): boolean { + canHydrateTextInstance( + instance: Instance | TextInstance, + text: string, + ): boolean { + if (text === '') { + // Empty strings are not parsed by HTML so there won't be a correct match here. + return false; + } return instance.nodeType === 3; }, @@ -401,11 +408,9 @@ var DOMRenderer = ReactFiberReconciler({ ): null | Instance | TextInstance { let node = instance.nextSibling; // Skip non-hydratable nodes. - /* while (node && node.nodeType !== 1 && node.nodeType !== 3) { node = node.nextSibling; } - */ return (node: any); }, @@ -414,11 +419,9 @@ var DOMRenderer = ReactFiberReconciler({ ): null | Instance | TextInstance { let next = parentInstance.firstChild; // Skip non-hydratable nodes. - /* while (next && next.nodeType !== 1 && next.nodeType !== 3) { next = next.nextSibling; } - */ return (next: any); }, diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js index 64947e423e..849f8ec6e5 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js @@ -606,8 +606,9 @@ describe('ReactDOMServerIntegration', () => { // with Fiber, there are just three separate text node children, // each of which is blank. if (render === serverRender || render === streamRender) { - // For plain server markup result we expect six comment nodes. - expect(e.childNodes.length).toBe(6); + // For plain server markup result we should have no text nodes if + // they're all empty. + expect(e.childNodes.length).toBe(0); expect(e.textContent).toBe(''); } else { expect(e.childNodes.length).toBe(3); @@ -627,12 +628,18 @@ describe('ReactDOMServerIntegration', () => { itRenders('a div with multiple whitespace children', async render => { const e = await render(
{' '}{' '}{' '}
); if (ReactDOMFeatureFlags.useFiber) { - // with Fiber, there are just three text nodes. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(9); - expectTextNode(e.childNodes[1], ' '); + // with Fiber, there are normally just three text nodes + if ( + render === serverRender || + render === clientRenderOnServerString || + render === streamRender + ) { + // For plain server markup result we have comments between. + // If we're able to hydrate, they remain. + expect(e.childNodes.length).toBe(5); + expectTextNode(e.childNodes[0], ' '); + expectTextNode(e.childNodes[2], ' '); expectTextNode(e.childNodes[4], ' '); - expectTextNode(e.childNodes[7], ' '); } else { expect(e.childNodes.length).toBe(3); expectTextNode(e.childNodes[0], ' '); @@ -652,10 +659,7 @@ describe('ReactDOMServerIntegration', () => { itRenders('a div with text sibling to a node', async render => { const e = await render(
TextMore Text
); let spanNode; - if ( - ReactDOMFeatureFlags.useFiber && - (render !== serverRender && render !== streamRender) - ) { + if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are only two children, the "Text" text node and // the span element. expect(e.childNodes.length).toBe(2); @@ -666,14 +670,7 @@ describe('ReactDOMServerIntegration', () => { expect(e.childNodes.length).toBe(4); spanNode = e.childNodes[3]; } - if ( - ReactDOMFeatureFlags.useFiber && - (render === serverRender || render === streamRender) - ) { - expectTextNode(e.childNodes[1], 'Text'); - } else { - expectTextNode(e.childNodes[0], 'Text'); - } + expectTextNode(e.childNodes[0], 'Text'); expect(spanNode.tagName).toBe('SPAN'); expect(spanNode.childNodes.length).toBe(1); expectNode(spanNode.firstChild, TEXT_NODE_TYPE, 'More Text'); @@ -698,8 +695,8 @@ describe('ReactDOMServerIntegration', () => { if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are just two text nodes. if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(5); - expectTextNode(e.childNodes[3], 'foo'); + expect(e.childNodes.length).toBe(1); + expectTextNode(e.childNodes[0], 'foo'); } else { expect(e.childNodes.length).toBe(2); expectTextNode(e.childNodes[0], ''); @@ -720,8 +717,8 @@ describe('ReactDOMServerIntegration', () => { if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are just two text nodes. if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(5); - expectTextNode(e.childNodes[1], 'foo'); + expect(e.childNodes.length).toBe(1); + expectTextNode(e.childNodes[0], 'foo'); } else { expect(e.childNodes.length).toBe(2); expectTextNode(e.childNodes[0], 'foo'); @@ -741,10 +738,15 @@ describe('ReactDOMServerIntegration', () => { const e = await render(
{'foo'}{'bar'}
); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are just two text nodes. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(6); - expectTextNode(e.childNodes[1], 'foo'); - expectTextNode(e.childNodes[4], 'bar'); + if ( + render === serverRender || + render === clientRenderOnServerString || + render === streamRender + ) { + // In the server render output there's a comment between them. + expect(e.childNodes.length).toBe(3); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[2], 'bar'); } else { expect(e.childNodes.length).toBe(2); expectTextNode(e.childNodes[0], 'foo'); @@ -776,10 +778,15 @@ describe('ReactDOMServerIntegration', () => { const e = await render(
{'foo'}{40}
); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are just two text nodes. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(6); - expectTextNode(e.childNodes[1], 'foo'); - expectTextNode(e.childNodes[4], '40'); + if ( + render === serverRender || + render === clientRenderOnServerString || + render === streamRender + ) { + // In the server markup there's a comment between. + expect(e.childNodes.length).toBe(3); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[2], '40'); } else { expect(e.childNodes.length).toBe(2); expectTextNode(e.childNodes[0], 'foo'); @@ -816,12 +823,7 @@ describe('ReactDOMServerIntegration', () => { const e = await render(
); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, an empty component results in no markup. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(1); - expectEmptyNode(e.firstChild); - } else { - expect(e.childNodes.length).toBe(0); - } + expect(e.childNodes.length).toBe(0); } else { // with Stack, an empty component results in one react-empty comment // node. @@ -834,13 +836,8 @@ describe('ReactDOMServerIntegration', () => { const e = await render(
{null}foo
); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there is just one text node. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(3); - expectTextNode(e.childNodes[1], 'foo'); - } else { - expect(e.childNodes.length).toBe(1); - expectTextNode(e.childNodes[0], 'foo'); - } + expect(e.childNodes.length).toBe(1); + expectTextNode(e.childNodes[0], 'foo'); } else { // with Stack, there's a text node surronded by react-text comment nodes. expect(e.childNodes.length).toBe(3); @@ -852,13 +849,8 @@ describe('ReactDOMServerIntegration', () => { const e = await render(
{false}foo
); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there is just one text node. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(3); - expectTextNode(e.childNodes[1], 'foo'); - } else { - expect(e.childNodes.length).toBe(1); - expectTextNode(e.childNodes[0], 'foo'); - } + expect(e.childNodes.length).toBe(1); + expectTextNode(e.childNodes[0], 'foo'); } else { // with Stack, there's a text node surronded by react-text comment nodes. expect(e.childNodes.length).toBe(3); @@ -870,13 +862,8 @@ describe('ReactDOMServerIntegration', () => { const e = await render(
{false}{null}foo{null}{false}
); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there is just one text node. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(3); - expectTextNode(e.childNodes[1], 'foo'); - } else { - expect(e.childNodes.length).toBe(1); - expectTextNode(e.childNodes[0], 'foo'); - } + expect(e.childNodes.length).toBe(1); + expectTextNode(e.childNodes[0], 'foo'); } else { // with Stack, there's a text node surronded by react-text comment nodes. expect(e.childNodes.length).toBe(3); @@ -1075,17 +1062,10 @@ describe('ReactDOMServerIntegration', () => { if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are three children: the child1 element, a // single space text node, and the child2 element. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(5); - child1 = e.childNodes[0]; - textNode = e.childNodes[2]; - child2 = e.childNodes[4]; - } else { - expect(e.childNodes.length).toBe(3); - child1 = e.childNodes[0]; - textNode = e.childNodes[1]; - child2 = e.childNodes[2]; - } + expect(e.childNodes.length).toBe(3); + child1 = e.childNodes[0]; + textNode = e.childNodes[1]; + child2 = e.childNodes[2]; } else { // with Stack, there are five children: the child1 element, a single // space surrounded by react-text comments, and the child2 element. @@ -1111,17 +1091,10 @@ describe('ReactDOMServerIntegration', () => { if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are three children: a one-space text node, the // child element, and a two-space text node. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(7); - textNode1 = e.childNodes[1]; - child = e.childNodes[3]; - textNode2 = e.childNodes[5]; - } else { - expect(e.childNodes.length).toBe(3); - textNode1 = e.childNodes[0]; - child = e.childNodes[1]; - textNode2 = e.childNodes[2]; - } + expect(e.childNodes.length).toBe(3); + textNode1 = e.childNodes[0]; + child = e.childNodes[1]; + textNode2 = e.childNodes[2]; } else { // with Stack, there are 7 children: a one-space text node surrounded // by react-text comments, the child element, and a two-space text node @@ -1153,10 +1126,14 @@ describe('ReactDOMServerIntegration', () => { ); if (ReactDOMFeatureFlags.useFiber) { // with Fiber, there are just two text nodes. - if (render === serverRender || render === streamRender) { - expect(e.childNodes.length).toBe(6); - expectTextNode(e.childNodes[1], 'Text1"'); - expectTextNode(e.childNodes[4], 'Text2"'); + if ( + render === serverRender || + render === clientRenderOnServerString || + render === streamRender + ) { + expect(e.childNodes.length).toBe(3); + expectTextNode(e.childNodes[0], 'Text1"'); + expectTextNode(e.childNodes[2], 'Text2"'); } else { expect(e.childNodes.length).toBe(2); expectTextNode(e.childNodes[0], 'Text1"'); diff --git a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js index 79cca198f0..53d81dac08 100644 --- a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js +++ b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js @@ -53,7 +53,7 @@ describe('ReactDOMServer', () => { ROOT_ATTRIBUTE_NAME + '="" ' + ID_ATTRIBUTE_NAME + - '="[^"]+" ' + + '="[^"]*" ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world', ), @@ -68,7 +68,7 @@ describe('ReactDOMServer', () => { ROOT_ATTRIBUTE_NAME + '="" ' + ID_ATTRIBUTE_NAME + - '="[^"]+" ' + + '="[^"]*" ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>', ), @@ -83,7 +83,7 @@ describe('ReactDOMServer', () => { ROOT_ATTRIBUTE_NAME + '="" ' + ID_ATTRIBUTE_NAME + - '="[^"]+" ' + + '="[^"]*" ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>', ), @@ -98,7 +98,11 @@ describe('ReactDOMServer', () => { } var response = ReactDOMServer.renderToString(); - expect(response).toBe(''); + if (ReactDOMFeatureFlags.useFiber) { + expect(response).toBe(''); + } else { + expect(response).toBe(''); + } }); // TODO: Test that listeners are not registered onto any document/container. @@ -123,14 +127,16 @@ describe('ReactDOMServer', () => { ROOT_ATTRIBUTE_NAME + '="" ' + ID_ATTRIBUTE_NAME + - '="[^"]+" ' + + '="[^"]*" ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' + '' + - 'My name is ' + - 'child' + + '="[^"]*">' + + (ReactDOMFeatureFlags.useFiber + ? 'My name is child' + : 'My name is ' + + 'child') + '' + '', ), @@ -190,11 +196,13 @@ describe('ReactDOMServer', () => { ROOT_ATTRIBUTE_NAME + '="" ' + ID_ATTRIBUTE_NAME + - '="[^"]+" ' + + '="[^"]*" ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' + - 'Component name: ' + - 'TestComponent' + + (ReactDOMFeatureFlags.useFiber + ? 'Component name: TestComponent' + : 'Component name: ' + + 'TestComponent') + '', ), ); diff --git a/src/renderers/shared/fiber/ReactFiberHydrationContext.js b/src/renderers/shared/fiber/ReactFiberHydrationContext.js index 920934fb6b..ab0b98fcb2 100644 --- a/src/renderers/shared/fiber/ReactFiberHydrationContext.js +++ b/src/renderers/shared/fiber/ReactFiberHydrationContext.js @@ -108,11 +108,12 @@ module.exports = function( switch (fiber.tag) { case HostComponent: { const type = fiber.type; - const props = fiber.memoizedProps; + const props = fiber.pendingProps; return canHydrateInstance(nextInstance, type, props); } case HostText: { - return canHydrateTextInstance(nextInstance); + const text = fiber.pendingProps; + return canHydrateTextInstance(nextInstance, text); } default: return false; diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 5358985348..71b5ff2fbb 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -123,7 +123,7 @@ export type HostConfig = { // Optional hydration canHydrateInstance?: (instance: I | TI, type: T, props: P) => boolean, - canHydrateTextInstance?: (instance: I | TI) => boolean, + canHydrateTextInstance?: (instance: I | TI, text: string) => boolean, getNextHydratableSibling?: (instance: I | TI) => null | I | TI, getFirstHydratableChild?: (parentInstance: I | C) => null | I | TI, hydrateInstance?: ( diff --git a/src/renderers/shared/server/ReactPartialRenderer.js b/src/renderers/shared/server/ReactPartialRenderer.js index 53ba1d9a37..2061ce4b66 100644 --- a/src/renderers/shared/server/ReactPartialRenderer.js +++ b/src/renderers/shared/server/ReactPartialRenderer.js @@ -181,7 +181,6 @@ function createOpenTagMarkup( props, makeStaticMarkup, isRootElement, - domID, instForDebug, ) { var ret = '<' + tagVerbatim; @@ -230,7 +229,7 @@ function createOpenTagMarkup( if (isRootElement) { ret += ' ' + DOMPropertyOperations.createMarkupForRoot(); } - ret += ' ' + DOMPropertyOperations.createMarkupForID(domID); + ret += ' ' + DOMPropertyOperations.createMarkupForID(''); return ret; } @@ -346,9 +345,9 @@ class ReactDOMServerRenderer { footer: '', }, ]; - this.idCounter = 1; this.exhausted = false; this.currentSelectValue = null; + this.previousWasTextNode = false; this.makeStaticMarkup = makeStaticMarkup; } @@ -366,6 +365,7 @@ class ReactDOMServerRenderer { var frame = this.stack[this.stack.length - 1]; if (frame.childIndex >= frame.children.length) { out += frame.footer; + this.previousWasTextNode = false; this.stack.pop(); if (frame.tag === 'select') { this.currentSelectValue = null; @@ -380,26 +380,22 @@ class ReactDOMServerRenderer { render(child, context) { if (typeof child === 'string' || typeof child === 'number') { - if (this.makeStaticMarkup) { - return escapeTextContentForBrowser('' + child); + var text = '' + child; + if (text === '') { + return ''; } - return ( - '' + - escapeTextContentForBrowser('' + child) + - '' - ); + if (this.makeStaticMarkup) { + return escapeTextContentForBrowser(text); + } + if (this.previousWasTextNode) { + return '' + escapeTextContentForBrowser(text); + } + this.previousWasTextNode = true; + return escapeTextContentForBrowser(text); } else { ({child, context} = resolve(child, context)); if (child === null || child === false) { - if (this.makeStaticMarkup) { - // Normally we'd insert a comment node, but since this is a situation - // where React won't take over (static pages), we can simply return - // nothing. - return ''; - } - return ''; + return ''; } else { return this.renderDOM(child, context); } @@ -645,7 +641,6 @@ class ReactDOMServerRenderer { props, this.makeStaticMarkup, this.stack.length === 1, - this.idCounter++, null, ); var footer = '';