@@ -119,6 +119,9 @@ const DataStreamingFormat: StreamingFormat = 1;
119
119
export type ResponseState = {
120
120
bootstrapChunks : Array < Chunk | PrecomputedChunk > ,
121
121
fallbackBootstrapChunks : void | Array < Chunk | PrecomputedChunk > ,
122
+ requiresEmbedding : boolean ,
123
+ hasHead : boolean ,
124
+ hasHtml : boolean ,
122
125
placeholderPrefix : PrecomputedChunk ,
123
126
segmentPrefix : PrecomputedChunk ,
124
127
boundaryPrefix : string ,
@@ -191,6 +194,7 @@ export function createResponseState(
191
194
> | void ,
192
195
externalRuntimeConfig : string | BootstrapScriptDescriptor | void ,
193
196
containerID : string | void ,
197
+ documentEmbedding : boolean | void ,
194
198
) : ResponseState {
195
199
const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix ;
196
200
const inlineScriptWithNonce =
@@ -327,6 +331,9 @@ export function createResponseState(
327
331
fallbackBootstrapChunks : fallbackBootstrapChunks . length
328
332
? fallbackBootstrapChunks
329
333
: undefined ,
334
+ requiresEmbedding : documentEmbedding === true ,
335
+ hasHead : false ,
336
+ hasHtml : false ,
330
337
placeholderPrefix : stringToPrecomputedChunk ( idPrefix + 'P :') ,
331
338
segmentPrefix : stringToPrecomputedChunk ( idPrefix + 'S :') ,
332
339
boundaryPrefix : idPrefix + 'B :',
@@ -1652,33 +1659,100 @@ function pushStartHead(
1652
1659
target : Array < Chunk | PrecomputedChunk > ,
1653
1660
preamble : Array < Chunk | PrecomputedChunk > ,
1654
1661
props : Object ,
1655
- tag : string ,
1656
1662
responseState : ResponseState ,
1657
1663
) : ReactNodeList {
1658
- return pushStartGenericElement (
1659
- enableFloat ? preamble : target ,
1660
- props ,
1661
- tag ,
1662
- responseState ,
1663
- ) ;
1664
+ if ( enableFloat ) {
1665
+ let children = null ;
1666
+ let innerHTML = null ;
1667
+ let includedAttributeProps = false ;
1668
+
1669
+ if ( ! responseState . hasHead ) {
1670
+ responseState . hasHead = true ;
1671
+ preamble . push ( startChunkForTag ( 'head' ) ) ;
1672
+ for ( const propKey in props ) {
1673
+ if ( hasOwnProperty . call ( props , propKey ) ) {
1674
+ const propValue = props [ propKey ] ;
1675
+ if ( propValue == null ) {
1676
+ continue ;
1677
+ }
1678
+ switch ( propKey ) {
1679
+ case 'children ':
1680
+ children = propValue ;
1681
+ break ;
1682
+ case 'dangerouslySetInnerHTML ':
1683
+ innerHTML = propValue ;
1684
+ break ;
1685
+ default :
1686
+ if ( __DEV__ ) {
1687
+ includedAttributeProps = true ;
1688
+ }
1689
+ pushAttribute ( preamble , responseState , propKey , propValue ) ;
1690
+ break ;
1691
+ }
1692
+ }
1693
+ }
1694
+ preamble . push ( endOfStartTag ) ;
1695
+ } else {
1696
+ // We elide the actual <head> tag because it was previously rendered but we still need
1697
+ // to render children/innerHTML
1698
+ for ( const propKey in props ) {
1699
+ if ( hasOwnProperty . call ( props , propKey ) ) {
1700
+ const propValue = props [ propKey ] ;
1701
+ if ( propValue == null ) {
1702
+ continue ;
1703
+ }
1704
+ switch ( propKey ) {
1705
+ case 'children ':
1706
+ children = propValue ;
1707
+ break ;
1708
+ case 'dangerouslySetInnerHTML ':
1709
+ innerHTML = propValue ;
1710
+ break ;
1711
+ default :
1712
+ if ( __DEV__ ) {
1713
+ includedAttributeProps = true ;
1714
+ }
1715
+ break ;
1716
+ }
1717
+ }
1718
+ }
1719
+ }
1720
+
1721
+ if ( __DEV__ ) {
1722
+ if ( ( responseState : any ) . isDocumentEmbedded && includedAttributeProps ) {
1723
+ // We use this embedded flag a heuristic for whether we are rendering with renderIntoDocument
1724
+ console . error (
1725
+ 'A <head> tag was rendered with props when using "renderIntoDocument". In this rendering mode' +
1726
+ ' React may emit the head tag early in some circumstances and therefore props on the <head> tag are not' +
1727
+ ' supported and may be missing in the rendered output for any particular render. In many cases props that' +
1728
+ ' are set on a <head> tag can be set on the <html> tag instead.' ,
1729
+ ) ;
1730
+ }
1731
+ }
1732
+
1733
+ pushInnerHTML ( target , innerHTML , children ) ;
1734
+ return children ;
1735
+ } else {
1736
+ return pushStartGenericElement ( target , props , 'head' , responseState ) ;
1737
+ }
1664
1738
}
1665
1739
1666
1740
function pushStartHtml (
1667
1741
target : Array < Chunk | PrecomputedChunk > ,
1668
1742
preamble : Array < Chunk | PrecomputedChunk > ,
1669
1743
props : Object ,
1670
- tag : string ,
1671
1744
responseState : ResponseState ,
1672
1745
formatContext : FormatContext ,
1673
1746
) : ReactNodeList {
1747
+ responseState . hasHtml = true ;
1674
1748
target = enableFloat ? preamble : target ;
1675
1749
if ( formatContext . insertionMode === ROOT_HTML_MODE ) {
1676
1750
// If we're rendering the html tag and we're at the root (i.e. not in foreignObject)
1677
1751
// then we also emit the DOCTYPE as part of the root content as a convenience for
1678
1752
// rendering the whole document.
1679
1753
target . push ( DOCTYPE ) ;
1680
1754
}
1681
- return pushStartGenericElement ( target , props , tag , responseState ) ;
1755
+ return pushStartGenericElement ( target , props , 'html' , responseState ) ;
1682
1756
}
1683
1757
1684
1758
function pushScript (
@@ -1756,6 +1830,25 @@ function pushScriptImpl(
1756
1830
return null ;
1757
1831
}
1758
1832
1833
+ function pushHtmlEmbedding (
1834
+ preamble : Array < Chunk | PrecomputedChunk > ,
1835
+ postamble : Array < Chunk | PrecomputedChunk > ,
1836
+ responseState : ResponseState ,
1837
+ ) : void {
1838
+ responseState . hasHtml = true ;
1839
+ preamble . push ( DOCTYPE ) ;
1840
+ preamble . push ( startChunkForTag ( 'html' ) , endOfStartTag ) ;
1841
+ postamble . push ( endTag1 , stringToChunk ( 'html' ) , endTag2 ) ;
1842
+ }
1843
+
1844
+ function pushBodyEmbedding (
1845
+ target : Array < Chunk | PrecomputedChunk > ,
1846
+ postamble : Array < Chunk | PrecomputedChunk > ,
1847
+ ) : void {
1848
+ target .push ( startChunkForTag ( 'body' ) , endOfStartTag ) ;
1849
+ postamble . push ( endTag1 , stringToChunk ( 'body' ) , endTag2 ) ;
1850
+ }
1851
+
1759
1852
function pushStartGenericElement (
1760
1853
target : Array < Chunk | PrecomputedChunk > ,
1761
1854
props : Object ,
@@ -1973,6 +2066,7 @@ const DOCTYPE: PrecomputedChunk = stringToPrecomputedChunk('<!DOCTYPE html>');
1973
2066
export function pushStartInstance (
1974
2067
target : Array < Chunk | PrecomputedChunk > ,
1975
2068
preamble : Array < Chunk | PrecomputedChunk > ,
2069
+ postamble : Array < Chunk | PrecomputedChunk > ,
1976
2070
type : string ,
1977
2071
props : Object ,
1978
2072
responseState : ResponseState ,
@@ -2016,6 +2110,31 @@ export function pushStartInstance(
2016
2110
}
2017
2111
}
2018
2112
2113
+ if ( enableFloat ) {
2114
+ if ( responseState . requiresEmbedding ) {
2115
+ responseState . requiresEmbedding = false ;
2116
+ if ( __DEV__ ) {
2117
+ // Dev only marker for later
2118
+ ( responseState : any ) . isDocumentEmbedded = true ;
2119
+ }
2120
+ switch ( type ) {
2121
+ case 'html ': {
2122
+ // noop
2123
+ break ;
2124
+ }
2125
+ case 'head ':
2126
+ case 'body ': {
2127
+ pushHtmlEmbedding ( preamble , postamble , responseState ) ;
2128
+ break ;
2129
+ }
2130
+ default : {
2131
+ pushBodyEmbedding ( target , postamble ) ;
2132
+ pushHtmlEmbedding ( preamble , postamble , responseState ) ;
2133
+ }
2134
+ }
2135
+ }
2136
+ }
2137
+
2019
2138
switch ( type ) {
2020
2139
// Special tags
2021
2140
case 'select ':
@@ -2105,13 +2224,12 @@ export function pushStartInstance(
2105
2224
}
2106
2225
// Preamble start tags
2107
2226
case 'head' :
2108
- return pushStartHead ( target , preamble , props , type , responseState ) ;
2227
+ return pushStartHead ( target , preamble , props , responseState ) ;
2109
2228
case 'html ': {
2110
2229
return pushStartHtml (
2111
2230
target ,
2112
2231
preamble ,
2113
2232
props ,
2114
- type ,
2115
2233
responseState ,
2116
2234
formatContext ,
2117
2235
) ;
@@ -2187,6 +2305,35 @@ export function pushEndInstance(
2187
2305
target . push ( endTag1 , stringToChunk ( type ) , endTag2 ) ;
2188
2306
}
2189
2307
2308
+ export function writePreambleOpen (
2309
+ destination : Destination ,
2310
+ preamble : Array < Chunk | PrecomputedChunk > ,
2311
+ responseState : ResponseState ,
2312
+ ) : void {
2313
+ for ( let i = 0 ; i < preamble . length ; i ++ ) {
2314
+ writeChunk ( destination , preamble [ i ] ) ;
2315
+ }
2316
+ preamble . length = 0 ;
2317
+ if ( enableFloat ) {
2318
+ if ( responseState . hasHtml && ! responseState . hasHead ) {
2319
+ responseState . hasHead = true ;
2320
+ writeChunk ( destination , startChunkForTag ( 'head' ) ) ;
2321
+ writeChunk ( destination , endOfStartTag ) ;
2322
+ preamble . push ( endTag1 , stringToChunk ( 'head' ) , endTag2 ) ;
2323
+ }
2324
+ }
2325
+ }
2326
+
2327
+ export function writePreambleClose (
2328
+ destination : Destination ,
2329
+ preamble : Array < Chunk | PrecomputedChunk > ,
2330
+ ) : void {
2331
+ for ( let i = 0 ; i < preamble . length ; i ++ ) {
2332
+ writeChunk ( destination , preamble [ i ] ) ;
2333
+ }
2334
+ preamble . length = 0 ;
2335
+ }
2336
+
2190
2337
export function writeCompletedRoot (
2191
2338
destination : Destination ,
2192
2339
responseState : ResponseState ,
0 commit comments