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
});