@@ -1294,21 +1294,102 @@ function pushStartMenuItem(
12941294 return null ;
12951295}
12961296
1297- function pushStartTitle (
1297+ function pushTitle (
12981298 target : Array < Chunk | PrecomputedChunk > ,
12991299 props : Object ,
13001300 responseState : ResponseState ,
13011301) : 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+
13021342 if ( enableFloat && resourcesFromElement ( 'title' , props ) ) {
13031343 // We have converted this link exclusively to a resource and no longer
13041344 // need to emit it
13051345 return null ;
13061346 }
1347+ return pushTitleImpl ( target , props , responseState ) ;
1348+ }
13071349
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 ;
13091390}
13101391
1311- function pushStartTitleImpl (
1392+ function pushStartTitle (
13121393 target : Array < Chunk | PrecomputedChunk > ,
13131394 props : Object ,
13141395 responseState : ResponseState ,
@@ -1340,7 +1421,7 @@ function pushStartTitleImpl(
13401421 target . push ( endOfStartTag ) ;
13411422
13421423 if ( __DEV__ ) {
1343- const child =
1424+ const childForValidation =
13441425 Array . isArray ( children ) && children . length < 2
13451426 ? children [ 0 ] || null
13461427 : children ;
@@ -1352,7 +1433,10 @@ function pushStartTitleImpl(
13521433 'will display markup and comments as text in the title and hydration will likely fail and ' +
13531434 'fall back to client rendering' ,
13541435 ) ;
1355- } else if ( child != null && child . $$typeof != null ) {
1436+ } else if (
1437+ childForValidation != null &&
1438+ childForValidation . $$typeof != null
1439+ ) {
13561440 console . error (
13571441 'A title element received a React element for children. ' +
13581442 'In the browser title Elements can only have Text Nodes as children. If ' +
@@ -1361,9 +1445,9 @@ function pushStartTitleImpl(
13611445 'fall back to client rendering' ,
13621446 ) ;
13631447 } else if (
1364- child != null &&
1365- typeof child !== 'string' &&
1366- typeof child !== 'number'
1448+ childForValidation != null &&
1449+ typeof childForValidation !== 'string' &&
1450+ typeof childForValidation !== 'number'
13671451 ) {
13681452 console . error (
13691453 'A title element received a value that was not a string or number for children. ' +
@@ -1374,6 +1458,7 @@ function pushStartTitleImpl(
13741458 ) ;
13751459 }
13761460 }
1461+
13771462 return children ;
13781463}
13791464
@@ -1410,12 +1495,12 @@ function pushStartHtml(
14101495 return pushStartGenericElement ( target , props , tag , responseState ) ;
14111496}
14121497
1413- function pushStartScript (
1498+ function pushScript (
14141499 target : Array < Chunk | PrecomputedChunk > ,
14151500 props : Object ,
14161501 responseState : ResponseState ,
14171502 textEmbedded : boolean ,
1418- ) : ReactNodeList {
1503+ ) : null {
14191504 if ( enableFloat && resourcesFromScript ( props ) ) {
14201505 if ( textEmbedded ) {
14211506 // 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(
14271512 return null ;
14281513 }
14291514
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 ;
14311570}
14321571
14331572function pushStartGenericElement (
@@ -1703,11 +1842,15 @@ export function pushStartInstance(
17031842 case 'menuitem ':
17041843 return pushStartMenuItem ( target , props , responseState ) ;
17051844 case 'title ':
1706- return pushStartTitle ( target , props , responseState ) ;
1845+ return enableFloat
1846+ ? pushTitle ( target , props , responseState )
1847+ : pushStartTitle ( target , props , responseState ) ;
17071848 case 'link ':
17081849 return pushLink ( target , props , responseState , textEmbedded ) ;
17091850 case 'script ':
1710- return pushStartScript ( target , props , responseState , textEmbedded ) ;
1851+ return enableFloat
1852+ ? pushScript ( target , props , responseState , textEmbedded )
1853+ : pushStartGenericElement ( target , props , type , responseState ) ;
17111854 case 'meta':
17121855 return pushMeta ( target , props , responseState , textEmbedded ) ;
17131856 // Newline eating tags
@@ -1777,9 +1920,19 @@ export function pushEndInstance(
17771920 props : Object ,
17781921) : void {
17791922 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+ }
17801932 // Omitted close tags
17811933 // TODO: Instead of repeating this switch we could try to pass a flag from above.
17821934 // That would require returning a tuple. Which might be ok if it gets inlined.
1935+ // eslint-disable-next-line-no-fallthrough
17831936 case 'area ':
17841937 case 'base ':
17851938 case 'br ':
@@ -2396,8 +2549,7 @@ export function writeInitialResources(
23962549
23972550 scripts . forEach ( r => {
23982551 // 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 ) ;
24012553 r . flushed = true ;
24022554 r . hint . flushed = true ;
24032555 } ) ;
@@ -2415,11 +2567,7 @@ export function writeInitialResources(
24152567 headResources . forEach ( r => {
24162568 switch ( r . type ) {
24172569 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 ) ;
24232571 break ;
24242572 }
24252573 case 'meta' : {
@@ -2516,11 +2664,7 @@ export function writeImmediateResources(
25162664 headResources . forEach ( r => {
25172665 switch ( r . type ) {
25182666 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 ) ;
25242668 break ;
25252669 }
25262670 case 'meta' : {
0 commit comments