refactor isHostResourceType to not receive the context from reconciler and not leak types (#25610)
type validateDOMNesting move `isHostResourceType` to ReactDOMHostConfig type `AncestorInfo` refactor `resourceFormOnly` into `ancestorInfo.containerTagInScope` provide hostContext from reconciler
Josh Story committed
Nov 1, 2022 at 17:14 UTC
4ea063b56ff7ccc69c4cc67c30fd6f31f9880464
5 files changed
+206
-172
packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
+2
-126
@@ -28,14 +28,8 @@ import {
28
getResourcesFromRoot,
29
markNodeAsResource,
30
} from './ReactDOMComponentTree';
31
-import {HTML_NAMESPACE} from '../shared/DOMNamespaces';
32
-import {
33
- getCurrentRootHostContainer,
34
- getHostContext,
35
-} from 'react-reconciler/src/ReactFiberHostContext';
36
-import {getResourceFormOnly} from './validateDOMNesting';
37
-import {getNamespace} from './ReactDOMHostConfig';
38
-import {SVG_NAMESPACE} from '../shared/DOMNamespaces';
31
+import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
32
+import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
33
34
// The resource types we support. currently they match the form for the as argument.
35
// In the future this may need to change, especially when modules / scripts are supported
@@ -1426,124 +1420,6 @@ function insertResourceInstanceBefore(
1420
}
1421
}
1422
1429
-export function isHostResourceType(type: string, props: Props): boolean {
1430
- let resourceFormOnly: boolean;
1431
- let namespace: string;
1432
- if (__DEV__) {
1433
- const hostContext = getHostContext();
1434
- resourceFormOnly = getResourceFormOnly(hostContext);
1435
- namespace = getNamespace(hostContext);
1436
- }
1437
- switch (type) {
1438
- case 'base':
1439
- case 'meta': {
1440
- return true;
1441
- }
1442
- case 'title': {
1443
- const hostContext = getHostContext();
1444
- return getNamespace(hostContext) !== SVG_NAMESPACE;
1445
- }
1446
- case 'link': {
1447
- const {onLoad, onError} = props;
1448
- if (onLoad || onError) {
1449
- if (__DEV__) {
1450
- if (resourceFormOnly) {
1451
- console.error(
1452
- 'Cannot render a <link> with onLoad or onError listeners outside the main document.' +
1453
- ' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1454
- ' somewhere in the <body>.',
1455
- );
1456
- } else if (namespace === SVG_NAMESPACE) {
1457
- console.error(
1458
- 'Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>.' +
1459
- ' Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor.',
1460
- );
1461
- }
1462
- }
1463
- return false;
1464
- }
1465
- switch (props.rel) {
1466
- case 'stylesheet': {
1467
- const {href, precedence, disabled} = props;
1468
- if (__DEV__) {
1469
- validateLinkPropsForStyleResource(props);
1470
- if (typeof precedence !== 'string') {
1471
- if (resourceFormOnly) {
1472
- console.error(
1473
- 'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
1474
- ' Consider adding precedence="default" or moving it into the root <head> tag.',
1475
- );
1476
- } else if (namespace === SVG_NAMESPACE) {
1477
- console.error(
1478
- 'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
1479
- ' Consider adding precedence="default" or moving it above the <svg> ancestor.',
1480
- );
1481
- }
1482
- }
1483
- }
1484
- return (
1485
- typeof href === 'string' &&
1486
- typeof precedence === 'string' &&
1487
- disabled == null
1488
- );
1489
- }
1490
- default: {
1491
- const {rel, href} = props;
1492
- return typeof href === 'string' && typeof rel === 'string';
1493
- }
1494
- }
1495
- }
1496
- case 'script': {
1497
- // We don't validate because it is valid to use async with onLoad/onError unlike combining
1498
- // precedence with these for style resources
1499
- const {src, async, onLoad, onError} = props;
1500
- if (__DEV__) {
1501
- if (async !== true) {
1502
- if (resourceFormOnly) {
1503
- console.error(
1504
- 'Cannot render a sync or defer <script> outside the main document without knowing its order.' +
1505
- ' Try adding async="" or moving it into the root <head> tag.',
1506
- );
1507
- } else if (namespace === SVG_NAMESPACE) {
1508
- console.error(
1509
- 'Cannot render a sync or defer <script> as a descendent of an <svg> element.' +
1510
- ' Try adding async="" or moving it above the ancestor <svg> element.',
1511
- );
1512
- }
1513
- } else if (onLoad || onError) {
1514
- if (resourceFormOnly) {
1515
- console.error(
1516
- 'Cannot render a <script> with onLoad or onError listeners outside the main document.' +
1517
- ' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1518
- ' somewhere in the <body>.',
1519
- );
1520
- } else if (namespace === SVG_NAMESPACE) {
1521
- console.error(
1522
- 'Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element.' +
1523
- ' Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element.',
1524
- );
1525
- }
1526
- }
1527
- }
1528
- return (async: any) && typeof src === 'string' && !onLoad && !onError;
1529
- }
1530
- case 'noscript':
1531
- case 'template':
1532
- case 'style': {
1533
- if (__DEV__) {
1534
- if (resourceFormOnly) {
1535
- console.error(
1536
- 'Cannot render <%s> outside the main document. Try moving it into the root <head> tag.',
1537
- type,
1538
- );
1539
- }
1540
- }
1541
- return false;
1542
- }
1543
- }
1544
- return false;
1545
-}
1546
-
1423
// When passing user input into querySelector(All) the embedded string must not alter
1424
// the semantics of the query. This escape function is safe to use when we know the
1425
// provided value is going to be wrapped in double quotes as part of an attribute selector
packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
+137
-20
@@ -16,6 +16,7 @@ import type {
16
ObserveVisibleRectsCallback,
17
} from 'react-reconciler/src/ReactTestSelectors';
18
import type {ReactScopeInstance} from 'shared/ReactTypes';
19
+import type {AncestorInfoDev} from './validateDOMNesting';
20
21
import {
22
precacheFiberNode,
@@ -47,13 +48,13 @@ import {
48
} from './ReactDOMComponent';
49
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
50
import setTextContent from './setTextContent';
50
-import {validateDOMNesting, updatedAncestorInfo} from './validateDOMNesting';
51
+import {validateDOMNesting, updatedAncestorInfoDev} from './validateDOMNesting';
52
import {
53
isEnabled as ReactBrowserEventEmitterIsEnabled,
54
setEnabled as ReactBrowserEventEmitterSetEnabled,
55
getEventPriority,
56
} from '../events/ReactDOMEventListener';
56
-import {getChildNamespace} from '../shared/DOMNamespaces';
57
+import {getChildNamespace, SVG_NAMESPACE} from '../shared/DOMNamespaces';
58
import {
59
ELEMENT_NODE,
60
TEXT_NODE,
@@ -89,8 +90,8 @@ import {
90
prepareToRenderResources,
91
cleanupAfterRenderResources,
92
clearRootResources,
92
- isHostResourceType,
93
} from './ReactDOMFloatClient';
94
+import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';
95
96
export type Type = string;
97
export type Props = {
@@ -107,6 +108,9 @@ export type Props = {
108
top?: null | number,
109
...
110
};
111
+type RawProps = {
112
+ [string]: mixed,
113
+};
114
export type EventTargetChildElement = {
115
type: string,
116
props: null | {
@@ -136,8 +140,7 @@ export type HydratableInstance = Instance | TextInstance | SuspenseInstance;
140
export type PublicInstance = Element | Text;
141
type HostContextDev = {
142
namespace: string,
139
- ancestorInfo: mixed,
140
- ...
143
+ ancestorInfo: AncestorInfoDev,
144
};
145
type HostContextProd = string;
146
export type HostContext = HostContextDev | HostContextProd;
@@ -193,7 +196,7 @@ export function getRootHostContext(
196
}
197
if (__DEV__) {
198
const validatedTag = type.toLowerCase();
196
- const ancestorInfo = updatedAncestorInfo(null, validatedTag);
199
+ const ancestorInfo = updatedAncestorInfoDev(null, validatedTag);
200
return {namespace, ancestorInfo};
201
}
202
return namespace;
@@ -206,7 +209,7 @@ export function getChildHostContext(
209
if (__DEV__) {
210
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
211
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
209
- const ancestorInfo = updatedAncestorInfo(
212
+ const ancestorInfo = updatedAncestorInfoDev(
213
parentHostContextDev.ancestorInfo,
214
type,
215
);
@@ -220,16 +223,6 @@ export function getPublicInstance(instance: Instance): Instance {
223
return instance;
224
}
225
223
-export function getNamespace(hostContext: HostContext): string {
224
- if (__DEV__) {
225
- const hostContextDev: HostContextDev = (hostContext: any);
226
- return hostContextDev.namespace;
227
- } else {
228
- const hostContextProd: HostContextProd = (hostContext: any);
229
- return hostContextProd;
230
- }
231
-}
232
-
226
export function prepareForCommit(containerInfo: Container): Object | null {
227
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
228
selectionInformation = getSelectionInformation();
@@ -287,7 +280,7 @@ export function createInstance(
280
typeof props.children === 'number'
281
) {
282
const string = '' + props.children;
290
- const ownAncestorInfo = updatedAncestorInfo(
283
+ const ownAncestorInfo = updatedAncestorInfoDev(
284
hostContextDev.ancestorInfo,
285
type,
286
);
@@ -350,7 +343,7 @@ export function prepareUpdate(
343
typeof newProps.children === 'number')
344
) {
345
const string = '' + newProps.children;
353
- const ownAncestorInfo = updatedAncestorInfo(
346
+ const ownAncestorInfo = updatedAncestorInfoDev(
347
hostContextDev.ancestorInfo,
348
type,
349
);
@@ -1573,7 +1566,131 @@ export function requestPostPaintCallback(callback: (time: number) => void) {
1566
1567
export const supportsResources = true;
1568
1576
-export {isHostResourceType};
1569
+export function isHostResourceType(
1570
+ type: string,
1571
+ props: RawProps,
1572
+ hostContext: HostContext,
1573
+): boolean {
1574
+ let outsideHostContainerContext: boolean;
1575
+ let namespace: string;
1576
+ if (__DEV__) {
1577
+ const hostContextDev: HostContextDev = (hostContext: any);
1578
+ // We can only render resources when we are not within the host container context
1579
+ outsideHostContainerContext = !hostContextDev.ancestorInfo
1580
+ .containerTagInScope;
1581
+ namespace = hostContextDev.namespace;
1582
+ } else {
1583
+ const hostContextProd: HostContextProd = (hostContext: any);
1584
+ namespace = hostContextProd;
1585
+ }
1586
+ switch (type) {
1587
+ case 'base':
1588
+ case 'meta': {
1589
+ return true;
1590
+ }
1591
+ case 'title': {
1592
+ return namespace !== SVG_NAMESPACE;
1593
+ }
1594
+ case 'link': {
1595
+ const {onLoad, onError} = props;
1596
+ if (onLoad || onError) {
1597
+ if (__DEV__) {
1598
+ if (outsideHostContainerContext) {
1599
+ console.error(
1600
+ 'Cannot render a <link> with onLoad or onError listeners outside the main document.' +
1601
+ ' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1602
+ ' somewhere in the <body>.',
1603
+ );
1604
+ } else if (namespace === SVG_NAMESPACE) {
1605
+ console.error(
1606
+ 'Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>.' +
1607
+ ' Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor.',
1608
+ );
1609
+ }
1610
+ }
1611
+ return false;
1612
+ }
1613
+ switch (props.rel) {
1614
+ case 'stylesheet': {
1615
+ const {href, precedence, disabled} = props;
1616
+ if (__DEV__) {
1617
+ validateLinkPropsForStyleResource(props);
1618
+ if (typeof precedence !== 'string') {
1619
+ if (outsideHostContainerContext) {
1620
+ console.error(
1621
+ 'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
1622
+ ' Consider adding precedence="default" or moving it into the root <head> tag.',
1623
+ );
1624
+ } else if (namespace === SVG_NAMESPACE) {
1625
+ console.error(
1626
+ 'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
1627
+ ' Consider adding precedence="default" or moving it above the <svg> ancestor.',
1628
+ );
1629
+ }
1630
+ }
1631
+ }
1632
+ return (
1633
+ typeof href === 'string' &&
1634
+ typeof precedence === 'string' &&
1635
+ disabled == null
1636
+ );
1637
+ }
1638
+ default: {
1639
+ const {rel, href} = props;
1640
+ return typeof href === 'string' && typeof rel === 'string';
1641
+ }
1642
+ }
1643
+ }
1644
+ case 'script': {
1645
+ // We don't validate because it is valid to use async with onLoad/onError unlike combining
1646
+ // precedence with these for style resources
1647
+ const {src, async, onLoad, onError} = props;
1648
+ if (__DEV__) {
1649
+ if (async !== true) {
1650
+ if (outsideHostContainerContext) {
1651
+ console.error(
1652
+ 'Cannot render a sync or defer <script> outside the main document without knowing its order.' +
1653
+ ' Try adding async="" or moving it into the root <head> tag.',
1654
+ );
1655
+ } else if (namespace === SVG_NAMESPACE) {
1656
+ console.error(
1657
+ 'Cannot render a sync or defer <script> as a descendent of an <svg> element.' +
1658
+ ' Try adding async="" or moving it above the ancestor <svg> element.',
1659
+ );
1660
+ }
1661
+ } else if (onLoad || onError) {
1662
+ if (outsideHostContainerContext) {
1663
+ console.error(
1664
+ 'Cannot render a <script> with onLoad or onError listeners outside the main document.' +
1665
+ ' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1666
+ ' somewhere in the <body>.',
1667
+ );
1668
+ } else if (namespace === SVG_NAMESPACE) {
1669
+ console.error(
1670
+ 'Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element.' +
1671
+ ' Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element.',
1672
+ );
1673
+ }
1674
+ }
1675
+ }
1676
+ return (async: any) && typeof src === 'string' && !onLoad && !onError;
1677
+ }
1678
+ case 'noscript':
1679
+ case 'template':
1680
+ case 'style': {
1681
+ if (__DEV__) {
1682
+ if (outsideHostContainerContext) {
1683
+ console.error(
1684
+ 'Cannot render <%s> outside the main document. Try moving it into the root <head> tag.',
1685
+ type,
1686
+ );
1687
+ }
1688
+ }
1689
+ return false;
1690
+ }
1691
+ }
1692
+ return false;
1693
+}
1694
1695
export function prepareRendererToRender(rootContainer: Container) {
1696
if (enableFloat) {
packages/react-dom-bindings/src/client/validateDOMNesting.js
+57
-22
@@ -3,11 +3,32 @@
3
*
4
* This source code is licensed under the MIT license found in the
5
* LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
*/
9
8
-let validateDOMNesting = () => {};
9
-let updatedAncestorInfo = () => {};
10
-let getResourceFormOnly = () => false;
10
+type ValidateDOMNesting = (?string, ?string, AncestorInfoDev) => void;
11
+let validateDOMNesting: ValidateDOMNesting = (() => {}: any);
12
+
13
+type UpdatedAncestorInfoDev = (?AncestorInfoDev, string) => AncestorInfoDev;
14
+let updatedAncestorInfoDev: UpdatedAncestorInfoDev = (() => {}: any);
15
+
16
+type Info = {tag: string};
17
+export type AncestorInfoDev = {
18
+ current: ?Info,
19
+
20
+ formTag: ?Info,
21
+ aTagInScope: ?Info,
22
+ buttonTagInScope: ?Info,
23
+ nobrTagInScope: ?Info,
24
+ pTagInButtonScope: ?Info,
25
+
26
+ listItemTagAutoclosing: ?Info,
27
+ dlItemTagAutoclosing: ?Info,
28
+
29
+ // <head> or <body>
30
+ containerTagInScope: ?Info,
31
+};
32
33
if (__DEV__) {
34
// This validation code was written based on the HTML5 parsing spec:
@@ -143,7 +164,7 @@ if (__DEV__) {
164
'rt',
165
];
166
146
- const emptyAncestorInfo = {
167
+ const emptyAncestorInfoDev: AncestorInfoDev = {
168
current: null,
169
170
formTag: null,
@@ -155,11 +176,11 @@ if (__DEV__) {
176
listItemTagAutoclosing: null,
177
dlItemTagAutoclosing: null,
178
158
- resourceFormOnly: true,
179
+ containerTagInScope: null,
180
};
181
161
- updatedAncestorInfo = function(oldInfo, tag) {
162
- const ancestorInfo = {...(oldInfo || emptyAncestorInfo)};
182
+ updatedAncestorInfoDev = function(oldInfo: ?AncestorInfoDev, tag: string) {
183
+ const ancestorInfo = {...(oldInfo || emptyAncestorInfoDev)};
184
const info = {tag};
185
186
if (inScopeTags.indexOf(tag) !== -1) {
@@ -183,10 +204,6 @@ if (__DEV__) {
204
ancestorInfo.dlItemTagAutoclosing = null;
205
}
206
186
- if (tag !== '#document' && tag !== 'html') {
187
- ancestorInfo.resourceFormOnly = false;
188
- }
189
-
207
ancestorInfo.current = info;
208
209
if (tag === 'form') {
@@ -210,6 +227,11 @@ if (__DEV__) {
227
if (tag === 'dd' || tag === 'dt') {
228
ancestorInfo.dlItemTagAutoclosing = info;
229
}
230
+ if (tag === '#document' || tag === 'html') {
231
+ ancestorInfo.containerTagInScope = null;
232
+ } else if (!ancestorInfo.containerTagInScope) {
233
+ ancestorInfo.containerTagInScope = info;
234
+ }
235
236
return ancestorInfo;
237
};
@@ -217,7 +239,10 @@ if (__DEV__) {
239
/**
240
* Returns whether
241
*/
220
- const isTagValidWithParent = function(tag, parentTag) {
242
+ const isTagValidWithParent = function(
243
+ tag: string,
244
+ parentTag: ?string,
245
+ ): boolean {
246
// First, let's check if we're in an unusual parsing mode...
247
switch (parentTag) {
248
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
@@ -342,7 +367,10 @@ if (__DEV__) {
367
/**
368
* Returns whether
369
*/
345
- const findInvalidAncestorForTag = function(tag, ancestorInfo) {
370
+ const findInvalidAncestorForTag = function(
371
+ tag: string,
372
+ ancestorInfo: AncestorInfoDev,
373
+ ): ?Info {
374
switch (tag) {
375
case 'address':
376
case 'article':
@@ -408,8 +436,12 @@ if (__DEV__) {
436
437
const didWarn = {};
438
411
- validateDOMNesting = function(childTag, childText, ancestorInfo) {
412
- ancestorInfo = ancestorInfo || emptyAncestorInfo;
439
+ validateDOMNesting = function(
440
+ childTag: ?string,
441
+ childText: ?string,
442
+ ancestorInfo: AncestorInfoDev,
443
+ ) {
444
+ ancestorInfo = ancestorInfo || emptyAncestorInfoDev;
445
const parentInfo = ancestorInfo.current;
446
const parentTag = parentInfo && parentInfo.tag;
447
@@ -420,6 +452,11 @@ if (__DEV__) {
452
);
453
}
454
childTag = '#text';
455
+ } else if (childTag == null) {
456
+ console.error(
457
+ 'validateDOMNesting: when childText or childTag must be provided',
458
+ );
459
+ return;
460
}
461
462
const invalidParent = isTagValidWithParent(childTag, parentTag)
@@ -435,7 +472,9 @@ if (__DEV__) {
472
473
const ancestorTag = invalidParentOrAncestor.tag;
474
438
- const warnKey = !!invalidParent + '|' + childTag + '|' + ancestorTag;
475
+ const warnKey =
476
+ // eslint-disable-next-line react-internal/safe-string-coercion
477
+ String(!!invalidParent) + '|' + childTag + '|' + ancestorTag;
478
if (didWarn[warnKey]) {
479
return;
480
}
@@ -444,7 +483,7 @@ if (__DEV__) {
483
let tagDisplayName = childTag;
484
let whitespaceInfo = '';
485
if (childTag === '#text') {
447
- if (/\S/.test(childText)) {
486
+ if (childText != null && /\S/.test(childText)) {
487
tagDisplayName = 'Text nodes';
488
} else {
489
tagDisplayName = 'Whitespace text nodes';
@@ -479,10 +518,6 @@ if (__DEV__) {
518
);
519
}
520
};
482
-
483
- getResourceFormOnly = hostContextDev => {
484
- return hostContextDev.ancestorInfo.resourceFormOnly;
485
- };
521
}
522
488
-export {updatedAncestorInfo, validateDOMNesting, getResourceFormOnly};
523
+export {updatedAncestorInfoDev, validateDOMNesting};
packages/react-reconciler/src/ReactFiber.new.js
+5
-2
@@ -108,6 +108,7 @@ import {
108
} from 'shared/ReactSymbols';
109
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new';
110
import {detachOffscreenInstance} from './ReactFiberCommitWork.new';
111
+import {getHostContext} from './ReactFiberHostContext.new';
112
113
export type {Fiber};
114
@@ -503,13 +504,15 @@ export function createFiberFromTypeAndProps(
504
enableHostSingletons &&
505
supportsSingletons
506
) {
506
- fiberTag = isHostResourceType(type, pendingProps)
507
+ const hostContext = getHostContext();
508
+ fiberTag = isHostResourceType(type, pendingProps, hostContext)
509
? HostResource
510
: isHostSingletonType(type)
511
? HostSingleton
512
: HostComponent;
513
} else if (enableFloat && supportsResources) {
512
- fiberTag = isHostResourceType(type, pendingProps)
514
+ const hostContext = getHostContext();
515
+ fiberTag = isHostResourceType(type, pendingProps, hostContext)
516
? HostResource
517
: HostComponent;
518
} else if (enableHostSingletons && supportsSingletons) {
packages/react-reconciler/src/ReactFiber.old.js
+5
-2
@@ -108,6 +108,7 @@ import {
108
} from 'shared/ReactSymbols';
109
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.old';
110
import {detachOffscreenInstance} from './ReactFiberCommitWork.old';
111
+import {getHostContext} from './ReactFiberHostContext.old';
112
113
export type {Fiber};
114
@@ -503,13 +504,15 @@ export function createFiberFromTypeAndProps(
504
enableHostSingletons &&
505
supportsSingletons
506
) {
506
- fiberTag = isHostResourceType(type, pendingProps)
507
+ const hostContext = getHostContext();
508
+ fiberTag = isHostResourceType(type, pendingProps, hostContext)
509
? HostResource
510
: isHostSingletonType(type)
511
? HostSingleton
512
: HostComponent;
513
} else if (enableFloat && supportsResources) {
512
- fiberTag = isHostResourceType(type, pendingProps)
514
+ const hostContext = getHostContext();
515
+ fiberTag = isHostResourceType(type, pendingProps, hostContext)
516
? HostResource
517
: HostComponent;
518
} else if (enableHostSingletons && supportsSingletons) {