@samitouri / QOS-React / commits / 864ae8fa98

Support comment node as a mount point (#9835)

This means you don't need an extra wrapper div for each component root you need. Rendered stuff is inserted before the comment you pass in.

Ben Alpert committed Jun 27, 2017 at 17:22 UTC 864ae8fa987bebfc43ea87476a03a44ac010ebce
3 files changed +89 -13
scripts/fiber/tests-passing.txt
+1
@@ -1433,6 +1433,7 @@ src/renderers/dom/shared/__tests__/ReactMount-test.js
1433 * should warn if the unmounted node was rendered by another copy of React
1434 * passes the correct callback context
1435 * initial mount is sync inside batchedUpdates, but task work is deferred until the end of the batch
1436 +* renders at a comment node
1437
1438 src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js
1439 * should destroy a react root upon request
src/renderers/dom/fiber/ReactDOMFiberEntry.js
+43 -13
@@ -33,6 +33,8 @@ var {isValidElement} = require('react');
33 var {injectInternals} = require('ReactFiberDevToolsHook');
34 var {
35 ELEMENT_NODE,
36 + TEXT_NODE,
37 + COMMENT_NODE,
38 DOCUMENT_NODE,
39 DOCUMENT_FRAGMENT_NODE,
40 } = require('HTMLNodeType');
@@ -99,7 +101,9 @@ function isValidContainer(node) {
101 return !!(node &&
102 (node.nodeType === ELEMENT_NODE ||
103 node.nodeType === DOCUMENT_NODE ||
102 - node.nodeType === DOCUMENT_FRAGMENT_NODE));
104 + node.nodeType === DOCUMENT_FRAGMENT_NODE ||
105 + (node.nodeType === COMMENT_NODE &&
106 + node.nodeValue === ' react-mount-point-unstable ')));
107 }
108
109 function getReactRootElementInContainer(container: any) {
@@ -141,8 +145,11 @@ var DOMRenderer = ReactFiberReconciler({
145 let root = (rootContainerInstance: any).documentElement;
146 namespace = root ? root.namespaceURI : getChildNamespace(null, '');
147 } else {
144 - const ownNamespace = (rootContainerInstance: any).namespaceURI || null;
145 - type = (rootContainerInstance: any).tagName;
148 + const container: any = rootContainerInstance.nodeType === COMMENT_NODE
149 + ? rootContainerInstance.parentNode
150 + : rootContainerInstance;
151 + const ownNamespace = container.namespaceURI || null;
152 + type = container.tagName;
153 namespace = getChildNamespace(ownNamespace, type);
154 }
155 if (__DEV__) {
@@ -354,7 +361,11 @@ var DOMRenderer = ReactFiberReconciler({
361 container: Container,
362 child: Instance | TextInstance,
363 ): void {
357 - container.appendChild(child);
364 + if (container.nodeType === COMMENT_NODE) {
365 + (container.parentNode: any).insertBefore(child, container);
366 + } else {
367 + container.appendChild(child);
368 + }
369 },
370
371 insertBefore(
@@ -370,7 +381,11 @@ var DOMRenderer = ReactFiberReconciler({
381 child: Instance | TextInstance,
382 beforeChild: Instance | TextInstance,
383 ): void {
373 - container.insertBefore(child, beforeChild);
384 + if (container.nodeType === COMMENT_NODE) {
385 + (container.parentNode: any).insertBefore(child, beforeChild);
386 + } else {
387 + container.insertBefore(child, beforeChild);
388 + }
389 },
390
391 removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
@@ -381,7 +396,11 @@ var DOMRenderer = ReactFiberReconciler({
396 container: Container,
397 child: Instance | TextInstance,
398 ): void {
384 - container.removeChild(child);
399 + if (container.nodeType === COMMENT_NODE) {
400 + (container.parentNode: any).removeChild(child);
401 + } else {
402 + container.removeChild(child);
403 + }
404 },
405
406 canHydrateInstance(
@@ -389,7 +408,10 @@ var DOMRenderer = ReactFiberReconciler({
408 type: string,
409 props: Props,
410 ): boolean {
392 - return instance.nodeType === 1 && type === instance.nodeName.toLowerCase();
411 + return (
412 + instance.nodeType === ELEMENT_NODE &&
413 + type === instance.nodeName.toLowerCase()
414 + );
415 },
416
417 canHydrateTextInstance(
@@ -400,7 +422,7 @@ var DOMRenderer = ReactFiberReconciler({
422 // Empty strings are not parsed by HTML so there won't be a correct match here.
423 return false;
424 }
403 - return instance.nodeType === 3;
425 + return instance.nodeType === TEXT_NODE;
426 },
427
428 getNextHydratableSibling(
@@ -408,7 +430,11 @@ var DOMRenderer = ReactFiberReconciler({
430 ): null | Instance | TextInstance {
431 let node = instance.nextSibling;
432 // Skip non-hydratable nodes.
411 - while (node && node.nodeType !== 1 && node.nodeType !== 3) {
433 + while (
434 + node &&
435 + node.nodeType !== ELEMENT_NODE &&
436 + node.nodeType !== TEXT_NODE
437 + ) {
438 node = node.nextSibling;
439 }
440 return (node: any);
@@ -419,7 +445,11 @@ var DOMRenderer = ReactFiberReconciler({
445 ): null | Instance | TextInstance {
446 let next = parentInstance.firstChild;
447 // Skip non-hydratable nodes.
422 - while (next && next.nodeType !== 1 && next.nodeType !== 3) {
448 + while (
449 + next &&
450 + next.nodeType !== ELEMENT_NODE &&
451 + next.nodeType !== TEXT_NODE
452 + ) {
453 next = next.nextSibling;
454 }
455 return (next: any);
@@ -483,9 +513,9 @@ function renderSubtreeIntoContainer(
513 );
514
515 warning(
486 - container.nodeType !== 1 ||
487 - !container.tagName ||
488 - container.tagName.toUpperCase() !== 'BODY',
516 + container.nodeType !== ELEMENT_NODE ||
517 + !((container: any): Element).tagName ||
518 + ((container: any): Element).tagName.toUpperCase() !== 'BODY',
519 'render(): Rendering components directly into document.body is ' +
520 'discouraged, since its children are often manipulated by third-party ' +
521 'scripts and browser extensions. This may lead to subtle ' +
src/renderers/dom/shared/__tests__/ReactMount-test.js
+45
@@ -11,6 +11,10 @@
11
12 'use strict';
13
14 +const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
15 +
16 +const invariant = require('invariant');
17 +
18 var React;
19 var ReactDOM;
20 var ReactDOMServer;
@@ -345,4 +349,45 @@ describe('ReactMount', () => {
349 expect(container1.textContent).toEqual('2');
350 expect(container2.textContent).toEqual('a!');
351 });
352 +
353 + if (ReactDOMFeatureFlags.useFiber) {
354 + describe('mount point is a comment node', () => {
355 + let containerDiv;
356 + let mountPoint;
357 +
358 + beforeEach(() => {
359 + const ReactFeatureFlags = require('ReactFeatureFlags');
360 + ReactFeatureFlags.disableNewFiberFeatures = false;
361 +
362 + containerDiv = document.createElement('div');
363 + containerDiv.innerHTML = 'A<!-- react-mount-point-unstable -->B';
364 + mountPoint = containerDiv.childNodes[1];
365 + invariant(mountPoint.nodeType === 8, 'Expected comment');
366 + });
367 +
368 + it('renders at a comment node', () => {
369 + function Char(props) {
370 + return props.children;
371 + }
372 + function list(chars) {
373 + return chars.split('').map(c => <Char key={c}>{c}</Char>);
374 + }
375 +
376 + ReactDOM.render(list('aeiou'), mountPoint);
377 + expect(containerDiv.innerHTML).toBe(
378 + 'Aaeiou<!-- react-mount-point-unstable -->B',
379 + );
380 +
381 + ReactDOM.render(list('yea'), mountPoint);
382 + expect(containerDiv.innerHTML).toBe(
383 + 'Ayea<!-- react-mount-point-unstable -->B',
384 + );
385 +
386 + ReactDOM.render(list(''), mountPoint);
387 + expect(containerDiv.innerHTML).toBe(
388 + 'A<!-- react-mount-point-unstable -->B',
389 + );
390 + });
391 + });
392 + }
393 });