@@ -123,6 +123,9 @@ const DataStreamingFormat: StreamingFormat = 1;
123123export type ResponseState = {
124124 bootstrapChunks : Array < Chunk | PrecomputedChunk > ,
125125 fallbackBootstrapChunks : void | Array < Chunk | PrecomputedChunk > ,
126+ requiresEmbedding : boolean ,
127+ hasHead : boolean ,
128+ hasHtml : boolean ,
126129 placeholderPrefix : PrecomputedChunk ,
127130 segmentPrefix : PrecomputedChunk ,
128131 boundaryPrefix : string ,
@@ -199,6 +202,7 @@ export function createResponseState(
199202 > | void ,
200203 externalRuntimeConfig : string | BootstrapScriptDescriptor | void ,
201204 containerID : string | void ,
205+ documentEmbedding : boolean | void ,
202206) : ResponseState {
203207 const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix ;
204208 const inlineScriptWithNonce =
@@ -335,6 +339,9 @@ export function createResponseState(
335339 fallbackBootstrapChunks : fallbackBootstrapChunks . length
336340 ? fallbackBootstrapChunks
337341 : undefined ,
342+ requiresEmbedding : documentEmbedding === true ,
343+ hasHead : false ,
344+ hasHtml : false ,
338345 placeholderPrefix : stringToPrecomputedChunk ( idPrefix + 'P :') ,
339346 segmentPrefix : stringToPrecomputedChunk ( idPrefix + 'S :') ,
340347 boundaryPrefix : idPrefix + 'B :',
@@ -1660,33 +1667,100 @@ function pushStartHead(
16601667 target : Array < Chunk | PrecomputedChunk > ,
16611668 preamble : Array < Chunk | PrecomputedChunk > ,
16621669 props : Object ,
1663- tag : string ,
16641670 responseState : ResponseState ,
16651671) : ReactNodeList {
1666- return pushStartGenericElement (
1667- enableFloat ? preamble : target ,
1668- props ,
1669- tag ,
1670- responseState ,
1671- ) ;
1672+ if ( enableFloat ) {
1673+ let children = null ;
1674+ let innerHTML = null ;
1675+ let includedAttributeProps = false ;
1676+
1677+ if ( ! responseState . hasHead ) {
1678+ responseState . hasHead = true ;
1679+ preamble . push ( startChunkForTag ( 'head' ) ) ;
1680+ for ( const propKey in props ) {
1681+ if ( hasOwnProperty . call ( props , propKey ) ) {
1682+ const propValue = props [ propKey ] ;
1683+ if ( propValue == null ) {
1684+ continue ;
1685+ }
1686+ switch ( propKey ) {
1687+ case 'children ':
1688+ children = propValue ;
1689+ break ;
1690+ case 'dangerouslySetInnerHTML ':
1691+ innerHTML = propValue ;
1692+ break ;
1693+ default :
1694+ if ( __DEV__ ) {
1695+ includedAttributeProps = true ;
1696+ }
1697+ pushAttribute ( preamble , responseState , propKey , propValue ) ;
1698+ break ;
1699+ }
1700+ }
1701+ }
1702+ preamble . push ( endOfStartTag ) ;
1703+ } else {
1704+ // We elide the actual <head> tag because it was previously rendered but we still need
1705+ // to render children/innerHTML
1706+ for ( const propKey in props ) {
1707+ if ( hasOwnProperty . call ( props , propKey ) ) {
1708+ const propValue = props [ propKey ] ;
1709+ if ( propValue == null ) {
1710+ continue ;
1711+ }
1712+ switch ( propKey ) {
1713+ case 'children ':
1714+ children = propValue ;
1715+ break ;
1716+ case 'dangerouslySetInnerHTML ':
1717+ innerHTML = propValue ;
1718+ break ;
1719+ default :
1720+ if ( __DEV__ ) {
1721+ includedAttributeProps = true ;
1722+ }
1723+ break ;
1724+ }
1725+ }
1726+ }
1727+ }
1728+
1729+ if ( __DEV__ ) {
1730+ if ( ( responseState : any ) . isDocumentEmbedded && includedAttributeProps ) {
1731+ // We use this embedded flag a heuristic for whether we are rendering with renderIntoDocument
1732+ console . error (
1733+ 'A <head> tag was rendered with props when using "renderIntoDocument". In this rendering mode' +
1734+ ' React may emit the head tag early in some circumstances and therefore props on the <head> tag are not' +
1735+ ' supported and may be missing in the rendered output for any particular render. In many cases props that' +
1736+ ' are set on a <head> tag can be set on the <html> tag instead.' ,
1737+ ) ;
1738+ }
1739+ }
1740+
1741+ pushInnerHTML ( target , innerHTML , children ) ;
1742+ return children ;
1743+ } else {
1744+ return pushStartGenericElement ( target , props , 'head' , responseState ) ;
1745+ }
16721746}
16731747
16741748function pushStartHtml (
16751749 target : Array < Chunk | PrecomputedChunk > ,
16761750 preamble : Array < Chunk | PrecomputedChunk > ,
16771751 props : Object ,
1678- tag : string ,
16791752 responseState : ResponseState ,
16801753 formatContext : FormatContext ,
16811754) : ReactNodeList {
1755+ responseState . hasHtml = true ;
16821756 target = enableFloat ? preamble : target ;
16831757 if ( formatContext . insertionMode === ROOT_HTML_MODE ) {
16841758 // If we're rendering the html tag and we're at the root (i.e. not in foreignObject)
16851759 // then we also emit the DOCTYPE as part of the root content as a convenience for
16861760 // rendering the whole document.
16871761 target . push ( DOCTYPE ) ;
16881762 }
1689- return pushStartGenericElement ( target , props , tag , responseState ) ;
1763+ return pushStartGenericElement ( target , props , 'html' , responseState ) ;
16901764}
16911765
16921766function pushScript (
@@ -1764,6 +1838,25 @@ function pushScriptImpl(
17641838 return null ;
17651839}
17661840
1841+ function pushHtmlEmbedding (
1842+ preamble : Array < Chunk | PrecomputedChunk > ,
1843+ postamble : Array < Chunk | PrecomputedChunk > ,
1844+ responseState : ResponseState ,
1845+ ) : void {
1846+ responseState . hasHtml = true ;
1847+ preamble . push ( DOCTYPE ) ;
1848+ preamble . push ( startChunkForTag ( 'html' ) , endOfStartTag ) ;
1849+ postamble . push ( endTag1 , stringToChunk ( 'html' ) , endTag2 ) ;
1850+ }
1851+
1852+ function pushBodyEmbedding (
1853+ target : Array < Chunk | PrecomputedChunk > ,
1854+ postamble : Array < Chunk | PrecomputedChunk > ,
1855+ ) : void {
1856+ target .push ( startChunkForTag ( 'body' ) , endOfStartTag ) ;
1857+ postamble . push ( endTag1 , stringToChunk ( 'body' ) , endTag2 ) ;
1858+ }
1859+
17671860function pushStartGenericElement (
17681861 target : Array < Chunk | PrecomputedChunk > ,
17691862 props : Object ,
@@ -1981,6 +2074,7 @@ const DOCTYPE: PrecomputedChunk = stringToPrecomputedChunk('<!DOCTYPE html>');
19812074export function pushStartInstance (
19822075 target : Array < Chunk | PrecomputedChunk > ,
19832076 preamble : Array < Chunk | PrecomputedChunk > ,
2077+ postamble : Array < Chunk | PrecomputedChunk > ,
19842078 type : string ,
19852079 props : Object ,
19862080 responseState : ResponseState ,
@@ -2024,6 +2118,31 @@ export function pushStartInstance(
20242118 }
20252119 }
20262120
2121+ if ( enableFloat ) {
2122+ if ( responseState . requiresEmbedding ) {
2123+ responseState . requiresEmbedding = false ;
2124+ if ( __DEV__ ) {
2125+ // Dev only marker for later
2126+ ( responseState : any ) . isDocumentEmbedded = true ;
2127+ }
2128+ switch ( type ) {
2129+ case 'html ': {
2130+ // noop
2131+ break ;
2132+ }
2133+ case 'head ':
2134+ case 'body ': {
2135+ pushHtmlEmbedding ( preamble , postamble , responseState ) ;
2136+ break ;
2137+ }
2138+ default : {
2139+ pushBodyEmbedding ( target , postamble ) ;
2140+ pushHtmlEmbedding ( preamble , postamble , responseState ) ;
2141+ }
2142+ }
2143+ }
2144+ }
2145+
20272146 switch ( type ) {
20282147 // Special tags
20292148 case 'select ':
@@ -2113,13 +2232,12 @@ export function pushStartInstance(
21132232 }
21142233 // Preamble start tags
21152234 case 'head' :
2116- return pushStartHead ( target , preamble , props , type , responseState ) ;
2235+ return pushStartHead ( target , preamble , props , responseState ) ;
21172236 case 'html ': {
21182237 return pushStartHtml (
21192238 target ,
21202239 preamble ,
21212240 props ,
2122- type ,
21232241 responseState ,
21242242 formatContext ,
21252243 ) ;
@@ -2195,6 +2313,35 @@ export function pushEndInstance(
21952313 target . push ( endTag1 , stringToChunk ( type ) , endTag2 ) ;
21962314}
21972315
2316+ export function writePreambleOpen (
2317+ destination : Destination ,
2318+ preamble : Array < Chunk | PrecomputedChunk > ,
2319+ responseState : ResponseState ,
2320+ ) : void {
2321+ for ( let i = 0 ; i < preamble . length ; i ++ ) {
2322+ writeChunk ( destination , preamble [ i ] ) ;
2323+ }
2324+ preamble . length = 0 ;
2325+ if ( enableFloat ) {
2326+ if ( responseState . hasHtml && ! responseState . hasHead ) {
2327+ responseState . hasHead = true ;
2328+ writeChunk ( destination , startChunkForTag ( 'head' ) ) ;
2329+ writeChunk ( destination , endOfStartTag ) ;
2330+ preamble . push ( endTag1 , stringToChunk ( 'head' ) , endTag2 ) ;
2331+ }
2332+ }
2333+ }
2334+
2335+ export function writePreambleClose (
2336+ destination : Destination ,
2337+ preamble : Array < Chunk | PrecomputedChunk > ,
2338+ ) : void {
2339+ for ( let i = 0 ; i < preamble . length ; i ++ ) {
2340+ writeChunk ( destination , preamble [ i ] ) ;
2341+ }
2342+ preamble . length = 0 ;
2343+ }
2344+
21982345export function writeCompletedRoot (
21992346 destination : Destination ,
22002347 responseState : ResponseState ,
0 commit comments