@@ -1294,21 +1294,102 @@ function pushStartMenuItem(
1294
1294
return null ;
1295
1295
}
1296
1296
1297
- function pushStartTitle (
1297
+ function pushTitle (
1298
1298
target : Array < Chunk | PrecomputedChunk > ,
1299
1299
props : Object ,
1300
1300
responseState : ResponseState ,
1301
1301
) : ReactNodeList {
1302
+ if ( __DEV__ ) {
1303
+ const children = props . children ;
1304
+ const childForValidation =
1305
+ Array . isArray ( children ) && children . length < 2
1306
+ ? children [ 0 ] || null
1307
+ : children ;
1308
+ if ( Array . isArray ( children ) && children . length > 1 ) {
1309
+ console . error (
1310
+ 'A title element received an array with more than 1 element as children. ' +
1311
+ 'In browsers title Elements can only have Text Nodes as children. If ' +
1312
+ 'the children being rendered output more than a single text node in aggregate the browser ' +
1313
+ 'will display markup and comments as text in the title and hydration will likely fail and ' +
1314
+ 'fall back to client rendering' ,
1315
+ ) ;
1316
+ } else if (
1317
+ childForValidation != null &&
1318
+ childForValidation . $$typeof != null
1319
+ ) {
1320
+ console . error (
1321
+ 'A title element received a React element for children. ' +
1322
+ 'In the browser title Elements can only have Text Nodes as children. If ' +
1323
+ 'the children being rendered output more than a single text node in aggregate the browser ' +
1324
+ 'will display markup and comments as text in the title and hydration will likely fail and ' +
1325
+ 'fall back to client rendering' ,
1326
+ ) ;
1327
+ } else if (
1328
+ childForValidation != null &&
1329
+ typeof childForValidation !== 'string' &&
1330
+ typeof childForValidation !== 'number'
1331
+ ) {
1332
+ console . error (
1333
+ 'A title element received a value that was not a string or number for children. ' +
1334
+ 'In the browser title Elements can only have Text Nodes as children. If ' +
1335
+ 'the children being rendered output more than a single text node in aggregate the browser ' +
1336
+ 'will display markup and comments as text in the title and hydration will likely fail and ' +
1337
+ 'fall back to client rendering' ,
1338
+ ) ;
1339
+ }
1340
+ }
1341
+
1302
1342
if ( enableFloat && resourcesFromElement ( 'title' , props ) ) {
1303
1343
// We have converted this link exclusively to a resource and no longer
1304
1344
// need to emit it
1305
1345
return null ;
1306
1346
}
1347
+ return pushTitleImpl ( target , props , responseState ) ;
1348
+ }
1307
1349
1308
- return pushStartTitleImpl ( target , props , responseState ) ;
1350
+ function pushTitleImpl (
1351
+ target : Array < Chunk | PrecomputedChunk > ,
1352
+ props : Object ,
1353
+ responseState : ResponseState ,
1354
+ ) : null {
1355
+ target . push ( startChunkForTag ( 'title' ) ) ;
1356
+
1357
+ let children = null ;
1358
+ for ( const propKey in props ) {
1359
+ if ( hasOwnProperty . call ( props , propKey ) ) {
1360
+ const propValue = props [ propKey ] ;
1361
+ if ( propValue == null ) {
1362
+ continue ;
1363
+ }
1364
+ switch ( propKey ) {
1365
+ case 'children ':
1366
+ children = propValue ;
1367
+ break ;
1368
+ case 'dangerouslySetInnerHTML ':
1369
+ throw new Error (
1370
+ '`dangerouslySetInnerHTML` does not make sense on <title>.' ,
1371
+ ) ;
1372
+ // eslint-disable-next-line-no-fallthrough
1373
+ default :
1374
+ pushAttribute ( target , responseState , propKey , propValue ) ;
1375
+ break ;
1376
+ }
1377
+ }
1378
+ }
1379
+ target . push ( endOfStartTag ) ;
1380
+
1381
+ const child =
1382
+ Array . isArray ( children ) && children . length < 2
1383
+ ? children [ 0 ] || null
1384
+ : children ;
1385
+ if ( typeof child === 'string' || typeof child === 'number' ) {
1386
+ target . push ( stringToChunk ( escapeTextForBrowser ( child ) ) ) ;
1387
+ }
1388
+ target . push ( endTag1 , stringToChunk ( 'title' ) , endTag2 ) ;
1389
+ return null ;
1309
1390
}
1310
1391
1311
- function pushStartTitleImpl (
1392
+ function pushStartTitle (
1312
1393
target : Array < Chunk | PrecomputedChunk > ,
1313
1394
props : Object ,
1314
1395
responseState : ResponseState ,
@@ -1340,7 +1421,7 @@ function pushStartTitleImpl(
1340
1421
target . push ( endOfStartTag ) ;
1341
1422
1342
1423
if ( __DEV__ ) {
1343
- const child =
1424
+ const childForValidation =
1344
1425
Array . isArray ( children ) && children . length < 2
1345
1426
? children [ 0 ] || null
1346
1427
: children ;
@@ -1352,7 +1433,10 @@ function pushStartTitleImpl(
1352
1433
'will display markup and comments as text in the title and hydration will likely fail and ' +
1353
1434
'fall back to client rendering' ,
1354
1435
) ;
1355
- } else if ( child != null && child . $$typeof != null ) {
1436
+ } else if (
1437
+ childForValidation != null &&
1438
+ childForValidation . $$typeof != null
1439
+ ) {
1356
1440
console . error (
1357
1441
'A title element received a React element for children. ' +
1358
1442
'In the browser title Elements can only have Text Nodes as children. If ' +
@@ -1361,9 +1445,9 @@ function pushStartTitleImpl(
1361
1445
'fall back to client rendering' ,
1362
1446
) ;
1363
1447
} else if (
1364
- child != null &&
1365
- typeof child !== 'string' &&
1366
- typeof child !== 'number'
1448
+ childForValidation != null &&
1449
+ typeof childForValidation !== 'string' &&
1450
+ typeof childForValidation !== 'number'
1367
1451
) {
1368
1452
console . error (
1369
1453
'A title element received a value that was not a string or number for children. ' +
@@ -1374,6 +1458,7 @@ function pushStartTitleImpl(
1374
1458
) ;
1375
1459
}
1376
1460
}
1461
+
1377
1462
return children ;
1378
1463
}
1379
1464
@@ -1410,12 +1495,12 @@ function pushStartHtml(
1410
1495
return pushStartGenericElement ( target , props , tag , responseState ) ;
1411
1496
}
1412
1497
1413
- function pushStartScript (
1498
+ function pushScript (
1414
1499
target : Array < Chunk | PrecomputedChunk > ,
1415
1500
props : Object ,
1416
1501
responseState : ResponseState ,
1417
1502
textEmbedded : boolean ,
1418
- ) : ReactNodeList {
1503
+ ) : null {
1419
1504
if ( enableFloat && resourcesFromScript ( props ) ) {
1420
1505
if ( textEmbedded ) {
1421
1506
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
@@ -1427,7 +1512,61 @@ function pushStartScript(
1427
1512
return null ;
1428
1513
}
1429
1514
1430
- return pushStartGenericElement ( target , props , 'script' , responseState ) ;
1515
+ return pushScriptImpl ( target , props , responseState ) ;
1516
+ }
1517
+
1518
+ function pushScriptImpl (
1519
+ target : Array < Chunk | PrecomputedChunk > ,
1520
+ props : Object ,
1521
+ responseState : ResponseState ,
1522
+ ) : null {
1523
+ target . push ( startChunkForTag ( 'script' ) ) ;
1524
+
1525
+ let children = null ;
1526
+ let innerHTML = null ;
1527
+ for ( const propKey in props ) {
1528
+ if ( hasOwnProperty . call ( props , propKey ) ) {
1529
+ const propValue = props [ propKey ] ;
1530
+ if ( propValue == null ) {
1531
+ continue ;
1532
+ }
1533
+ switch ( propKey ) {
1534
+ case 'children ':
1535
+ children = propValue ;
1536
+ break ;
1537
+ case 'dangerouslySetInnerHTML ':
1538
+ innerHTML = propValue ;
1539
+ break ;
1540
+ default :
1541
+ pushAttribute ( target , responseState , propKey , propValue ) ;
1542
+ break ;
1543
+ }
1544
+ }
1545
+ }
1546
+ target . push ( endOfStartTag ) ;
1547
+
1548
+ if ( __DEV__ ) {
1549
+ if ( children != null && typeof children !== 'string' ) {
1550
+ const descriptiveStatement =
1551
+ typeof children === 'number'
1552
+ ? 'a number for children'
1553
+ : Array . isArray ( children )
1554
+ ? 'an array for children'
1555
+ : 'something unexpected for children' ;
1556
+ console . error (
1557
+ 'A script element was rendered with %s. If script element has children it must be a single string.' +
1558
+ ' Consider using dangerouslySetInnerHTML or passing a plain string as children.' ,
1559
+ descriptiveStatement ,
1560
+ ) ;
1561
+ }
1562
+ }
1563
+
1564
+ pushInnerHTML ( target , innerHTML , children ) ;
1565
+ if ( typeof children === 'string' ) {
1566
+ target . push ( stringToChunk ( encodeHTMLTextNode ( children ) ) ) ;
1567
+ }
1568
+ target . push ( endTag1 , stringToChunk ( 'script' ) , endTag2 ) ;
1569
+ return null ;
1431
1570
}
1432
1571
1433
1572
function pushStartGenericElement (
@@ -1703,11 +1842,15 @@ export function pushStartInstance(
1703
1842
case 'menuitem ':
1704
1843
return pushStartMenuItem ( target , props , responseState ) ;
1705
1844
case 'title ':
1706
- return pushStartTitle ( target , props , responseState ) ;
1845
+ return enableFloat
1846
+ ? pushTitle ( target , props , responseState )
1847
+ : pushStartTitle ( target , props , responseState ) ;
1707
1848
case 'link ':
1708
1849
return pushLink ( target , props , responseState , textEmbedded ) ;
1709
1850
case 'script ':
1710
- return pushStartScript ( target , props , responseState , textEmbedded ) ;
1851
+ return enableFloat
1852
+ ? pushScript ( target , props , responseState , textEmbedded )
1853
+ : pushStartGenericElement ( target , props , type , responseState ) ;
1711
1854
case 'meta':
1712
1855
return pushMeta ( target , props , responseState , textEmbedded ) ;
1713
1856
// Newline eating tags
@@ -1777,9 +1920,19 @@ export function pushEndInstance(
1777
1920
props : Object ,
1778
1921
) : void {
1779
1922
switch ( type ) {
1923
+ // When float is on we expect title and script tags to always be pushed in
1924
+ // a unit and never return children. when we end up pushing the end tag we
1925
+ // want to ensure there is no extra closing tag pushed
1926
+ case 'title ':
1927
+ case 'script ': {
1928
+ if ( ! enableFloat ) {
1929
+ break ;
1930
+ }
1931
+ }
1780
1932
// Omitted close tags
1781
1933
// TODO: Instead of repeating this switch we could try to pass a flag from above.
1782
1934
// That would require returning a tuple. Which might be ok if it gets inlined.
1935
+ // eslint-disable-next-line-no-fallthrough
1783
1936
case 'area ':
1784
1937
case 'base ':
1785
1938
case 'br ':
@@ -2396,8 +2549,7 @@ export function writeInitialResources(
2396
2549
2397
2550
scripts . forEach ( r => {
2398
2551
// should never be flushed already
2399
- pushStartGenericElement ( target , r . props , 'script' , responseState ) ;
2400
- pushEndInstance ( target , target , 'script' , r . props ) ;
2552
+ pushScriptImpl ( target , r . props , responseState ) ;
2401
2553
r . flushed = true ;
2402
2554
r . hint . flushed = true ;
2403
2555
} ) ;
@@ -2415,11 +2567,7 @@ export function writeInitialResources(
2415
2567
headResources . forEach ( r => {
2416
2568
switch ( r . type ) {
2417
2569
case 'title' : {
2418
- pushStartTitleImpl ( target , r . props , responseState ) ;
2419
- if ( typeof r . props . children === 'string' ) {
2420
- target . push ( stringToChunk ( escapeTextForBrowser ( r . props . children ) ) ) ;
2421
- }
2422
- pushEndInstance ( target , target , 'title' , r . props ) ;
2570
+ pushTitleImpl ( target , r . props , responseState ) ;
2423
2571
break ;
2424
2572
}
2425
2573
case 'meta' : {
@@ -2516,11 +2664,7 @@ export function writeImmediateResources(
2516
2664
headResources . forEach ( r => {
2517
2665
switch ( r . type ) {
2518
2666
case 'title' : {
2519
- pushStartTitleImpl ( target , r . props , responseState ) ;
2520
- if ( typeof r . props . children === 'string' ) {
2521
- target . push ( stringToChunk ( escapeTextForBrowser ( r . props . children ) ) ) ;
2522
- }
2523
- pushEndInstance ( target , target , 'title' , r . props ) ;
2667
+ pushTitleImpl ( target , r . props , responseState ) ;
2524
2668
break ;
2525
2669
}
2526
2670
case 'meta' : {
0 commit comments