Skip to content

Commit

Permalink
[Float][Fizz][Legacy] hoisted elements no longer emit before <html>
Browse files Browse the repository at this point in the history
… in legacy apis such as `renderToString()` (#27269)

renderToString is a legacy server API which used a trick to avoid having
the DOCTYPE included when rendering full documents by setting the root
formatcontext to HTML_MODE rather than ROOT_HTML_MODE. Previously this
was of little consequence but with Float the Root mode started to be
used for things like determining if we could flush hoistable elements
yet. In issue #27177 we see that hoisted elements can appear before the
<html> tag when using a legacy API `renderToString`.

This change exports a DOCTYPE from FizzConfigDOM and FizzConfigDOMLegacy
respectively, using an empty chunk in the legacy case. The only runtime
perf cost here is that for legacy APIs there is an extra empty chunk to
write when rendering a top level <html> tag which is trivial enough

Fixes #27177

DiffTrain build for [86198b9](86198b9)
  • Loading branch information
gnoff committed Aug 22, 2023
1 parent c1e19bb commit f51a814
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 33 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dd480ef923930c8906a02664b01bcdea50707b5d
86198b923199224b60533952b636348bb0484a6d
26 changes: 14 additions & 12 deletions compiled/facebook-www/ReactDOMServer-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (__DEV__) {
var React = require("react");
var ReactDOM = require("react-dom");

var ReactVersion = "18.3.0-www-classic-5c145eb3";
var ReactVersion = "18.3.0-www-classic-2d41a773";

// This refers to a WWW module.
var warningWWW = require("warning");
Expand Down Expand Up @@ -2130,6 +2130,16 @@ function createFormatContext(insertionMode, selectedValue, noscriptTagInScope) {
noscriptTagInScope: noscriptTagInScope
};
}

function createRootFormatContext(namespaceURI) {
var insertionMode =
namespaceURI === "http://www.w3.org/2000/svg"
? SVG_MODE
: namespaceURI === "http://www.w3.org/1998/Math/MathML"
? MATHML_MODE
: ROOT_HTML_MODE;
return createFormatContext(insertionMode, null, false);
}
function getChildFormatContext(parentContext, type, props) {
switch (type) {
case "noscript":
Expand Down Expand Up @@ -4255,7 +4265,7 @@ function pushStartHtml(target, props, responseState, insertionMode) {
{
if (insertionMode === ROOT_HTML_MODE && responseState.htmlChunks === null) {
// This <html> is the Document.documentElement and should be part of the preamble
responseState.htmlChunks = [DOCTYPE];
responseState.htmlChunks = [doctypeChunk];
return pushStartGenericElement(responseState.htmlChunks, props, "html");
} else {
// This <html> is deep and is likely just an error. we emit it inline though.
Expand Down Expand Up @@ -4614,8 +4624,6 @@ function startChunkForTag(tag) {

return tagStartChunk;
}

var DOCTYPE = stringToPrecomputedChunk("<!DOCTYPE html>");
function pushStartInstance(
target,
type,
Expand Down Expand Up @@ -7093,14 +7101,8 @@ function createResponseState(
generateStaticMarkup: generateStaticMarkup
};
}
function createRootFormatContext() {
return {
insertionMode: HTML_MODE,
// We skip the root mode because we don't want to emit the DOCTYPE in legacy mode.
selectedValue: null,
noscriptTagInScope: false
};
}

var doctypeChunk = stringToPrecomputedChunk("");
function pushTextInstance(target, text, responseState, textEmbedded) {
if (responseState.generateStaticMarkup) {
target.push(stringToChunk(escapeTextForBrowser(text)));
Expand Down
26 changes: 14 additions & 12 deletions compiled/facebook-www/ReactDOMServer-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (__DEV__) {
var React = require("react");
var ReactDOM = require("react-dom");

var ReactVersion = "18.3.0-www-modern-0fdce021";
var ReactVersion = "18.3.0-www-modern-283d577d";

// This refers to a WWW module.
var warningWWW = require("warning");
Expand Down Expand Up @@ -2130,6 +2130,16 @@ function createFormatContext(insertionMode, selectedValue, noscriptTagInScope) {
noscriptTagInScope: noscriptTagInScope
};
}

function createRootFormatContext(namespaceURI) {
var insertionMode =
namespaceURI === "http://www.w3.org/2000/svg"
? SVG_MODE
: namespaceURI === "http://www.w3.org/1998/Math/MathML"
? MATHML_MODE
: ROOT_HTML_MODE;
return createFormatContext(insertionMode, null, false);
}
function getChildFormatContext(parentContext, type, props) {
switch (type) {
case "noscript":
Expand Down Expand Up @@ -4255,7 +4265,7 @@ function pushStartHtml(target, props, responseState, insertionMode) {
{
if (insertionMode === ROOT_HTML_MODE && responseState.htmlChunks === null) {
// This <html> is the Document.documentElement and should be part of the preamble
responseState.htmlChunks = [DOCTYPE];
responseState.htmlChunks = [doctypeChunk];
return pushStartGenericElement(responseState.htmlChunks, props, "html");
} else {
// This <html> is deep and is likely just an error. we emit it inline though.
Expand Down Expand Up @@ -4614,8 +4624,6 @@ function startChunkForTag(tag) {

return tagStartChunk;
}

var DOCTYPE = stringToPrecomputedChunk("<!DOCTYPE html>");
function pushStartInstance(
target,
type,
Expand Down Expand Up @@ -7093,14 +7101,8 @@ function createResponseState(
generateStaticMarkup: generateStaticMarkup
};
}
function createRootFormatContext() {
return {
insertionMode: HTML_MODE,
// We skip the root mode because we don't want to emit the DOCTYPE in legacy mode.
selectedValue: null,
noscriptTagInScope: false
};
}

var doctypeChunk = stringToPrecomputedChunk("");
function pushTextInstance(target, text, responseState, textEmbedded) {
if (responseState.generateStaticMarkup) {
target.push(stringToChunk(escapeTextForBrowser(text)));
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactDOMServer-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -1396,7 +1396,7 @@ function pushStartInstance(
0 === formatContext.insertionMode &&
null === responseState.htmlChunks
) {
responseState.htmlChunks = ["<!DOCTYPE html>"];
responseState.htmlChunks = [""];
var JSCompiler_inline_result$jscomp$6 = pushStartGenericElement(
responseState.htmlChunks,
props,
Expand Down Expand Up @@ -4164,7 +4164,7 @@ function renderToStringImpl(
options ? options.identifierPrefix : void 0,
unstable_externalRuntimeSrc
),
{ insertionMode: 2, selectedValue: null, noscriptTagInScope: !1 },
createFormatContext(0, null, !1),
Infinity,
onError,
void 0,
Expand Down Expand Up @@ -4215,4 +4215,4 @@ exports.renderToString = function (children, options) {
'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server'
);
};
exports.version = "18.3.0-www-classic-5a0762c7";
exports.version = "18.3.0-www-classic-1ad72241";
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactDOMServer-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -1396,7 +1396,7 @@ function pushStartInstance(
0 === formatContext.insertionMode &&
null === responseState.htmlChunks
) {
responseState.htmlChunks = ["<!DOCTYPE html>"];
responseState.htmlChunks = [""];
var JSCompiler_inline_result$jscomp$6 = pushStartGenericElement(
responseState.htmlChunks,
props,
Expand Down Expand Up @@ -4147,7 +4147,7 @@ function renderToStringImpl(
options ? options.identifierPrefix : void 0,
unstable_externalRuntimeSrc
),
{ insertionMode: 2, selectedValue: null, noscriptTagInScope: !1 },
createFormatContext(0, null, !1),
Infinity,
onError,
void 0,
Expand Down Expand Up @@ -4198,4 +4198,4 @@ exports.renderToString = function (children, options) {
'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server'
);
};
exports.version = "18.3.0-www-modern-bd3b7799";
exports.version = "18.3.0-www-modern-a4787be6";
4 changes: 2 additions & 2 deletions compiled/facebook-www/ReactDOMServerStreaming-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -4262,7 +4262,7 @@ function pushStartHtml(target, props, responseState, insertionMode) {
{
if (insertionMode === ROOT_HTML_MODE && responseState.htmlChunks === null) {
// This <html> is the Document.documentElement and should be part of the preamble
responseState.htmlChunks = [DOCTYPE];
responseState.htmlChunks = [doctypeChunk];
return pushStartGenericElement(responseState.htmlChunks, props, "html");
} else {
// This <html> is deep and is likely just an error. we emit it inline though.
Expand Down Expand Up @@ -4622,7 +4622,7 @@ function startChunkForTag(tag) {
return tagStartChunk;
}

var DOCTYPE = stringToPrecomputedChunk("<!DOCTYPE html>");
var doctypeChunk = stringToPrecomputedChunk("<!DOCTYPE html>");
function pushStartInstance(
target,
type,
Expand Down

0 comments on commit f51a814

Please sign in to comment.