diff --git a/compiled/facebook-www/REVISION b/compiled/facebook-www/REVISION index fd582252d49aa..a8a00743cbb02 100644 --- a/compiled/facebook-www/REVISION +++ b/compiled/facebook-www/REVISION @@ -1 +1 @@ -f7aa5e0aa3e2aa51279af4b6cb5413912cacd7f5 +2ec2aaea98588178525f83495669e11e96815a00 diff --git a/compiled/facebook-www/ReactART-dev.classic.js b/compiled/facebook-www/ReactART-dev.classic.js index f7588bee0bcef..0a13d82a0bab4 100644 --- a/compiled/facebook-www/ReactART-dev.classic.js +++ b/compiled/facebook-www/ReactART-dev.classic.js @@ -66,7 +66,7 @@ if (__DEV__) { return self; } - var ReactVersion = "19.0.0-www-classic-6c991e9f"; + var ReactVersion = "19.0.0-www-classic-be0d91b4"; var LegacyRoot = 0; var ConcurrentRoot = 1; @@ -3719,8 +3719,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } var isHydrating = false; // This flag allows for warning supression when we expect there to be mismatches @@ -3791,7 +4446,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + diff --git a/compiled/facebook-www/ReactART-dev.modern.js b/compiled/facebook-www/ReactART-dev.modern.js index 30fcf3b51c5e3..5fb293ca8319d 100644 --- a/compiled/facebook-www/ReactART-dev.modern.js +++ b/compiled/facebook-www/ReactART-dev.modern.js @@ -66,7 +66,7 @@ if (__DEV__) { return self; } - var ReactVersion = "19.0.0-www-modern-515161fd"; + var ReactVersion = "19.0.0-www-modern-71808898"; var LegacyRoot = 0; var ConcurrentRoot = 1; @@ -3484,8 +3484,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } var isHydrating = false; // This flag allows for warning supression when we expect there to be mismatches @@ -3556,7 +4211,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + diff --git a/compiled/facebook-www/ReactDOM-dev.classic.js b/compiled/facebook-www/ReactDOM-dev.classic.js index 5ba6e30578886..46526c4bde1a8 100644 --- a/compiled/facebook-www/ReactDOM-dev.classic.js +++ b/compiled/facebook-www/ReactDOM-dev.classic.js @@ -8147,8 +8147,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } // This may have been an insertion or a hydration. @@ -8165,7 +8820,7 @@ if (__DEV__) { var hydrationErrors = null; var rootOrSingletonContext = false; // Builds a common ancestor tree from the root down for collecting diffs. - function buildHydrationDiffNode(fiber) { + function buildHydrationDiffNode(fiber, distanceFromLeaf) { if (fiber.return === null) { // We're at the root. if (hydrationDiffRootDEV === null) { @@ -8173,18 +8828,24 @@ if (__DEV__) { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; } else if (hydrationDiffRootDEV.fiber !== fiber) { throw new Error( "Saw multiple hydration diff roots in a pass. This is a bug in React." ); + } else if (hydrationDiffRootDEV.distanceFromLeaf > distanceFromLeaf) { + hydrationDiffRootDEV.distanceFromLeaf = distanceFromLeaf; } return hydrationDiffRootDEV; } - var siblings = buildHydrationDiffNode(fiber.return).children; // The same node may already exist in the parent. Since we currently always render depth first + var siblings = buildHydrationDiffNode( + fiber.return, + distanceFromLeaf + 1 + ).children; // The same node may already exist in the parent. Since we currently always render depth first // and rerender if we suspend or terminate early, if a shared ancestor was added we should still // be inside of that shared ancestor which means it was the last one to be added. If this changes // we may have to scan the whole set. @@ -8193,14 +8854,21 @@ if (__DEV__) { siblings.length > 0 && siblings[siblings.length - 1].fiber === fiber ) { - return siblings[siblings.length - 1]; + var existing = siblings[siblings.length - 1]; + + if (existing.distanceFromLeaf > distanceFromLeaf) { + existing.distanceFromLeaf = distanceFromLeaf; + } + + return existing; } var newNode = { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; siblings.push(newNode); return newNode; @@ -8266,7 +8934,7 @@ if (__DEV__) { } } - function warnNonHydratedInstance(fiber) { + function warnNonHydratedInstance(fiber, rejectedCandidate) { { if (didSuspendOrErrorDEV) { // Inside a boundary that already suspended. We're currently rendering the @@ -8275,13 +8943,19 @@ if (__DEV__) { return; } // Add this fiber to the diff tree. - var diffNode = buildHydrationDiffNode(fiber); // We use null as a signal that there was no node to match. + var diffNode = buildHydrationDiffNode(fiber, 0); // We use null as a signal that there was no node to match. diffNode.serverProps = null; + + if (rejectedCandidate !== null) { + var description = + describeHydratableInstanceForDevWarnings(rejectedCandidate); + diffNode.serverTail.push(description); + } } } - function tryHydrateInstance(fiber, nextInstance) { + function tryHydrateInstance(fiber, nextInstance, hostContext) { // fiber is a HostComponent Fiber var instance = canHydrateInstance( nextInstance, @@ -8292,6 +8966,23 @@ if (__DEV__) { if (instance !== null) { fiber.stateNode = instance; + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + hostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; nextHydratableInstance = getFirstHydratableChild(instance); rootOrSingletonContext = false; @@ -8363,7 +9054,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - diff = describeDiff(); + diff = describeDiff(diffRoot); } } @@ -8398,6 +9089,23 @@ if (__DEV__) { currentHostContext, false )); + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + currentHostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; rootOrSingletonContext = true; nextHydratableInstance = getFirstHydratableChild(instance); @@ -8417,9 +9125,12 @@ if (__DEV__) { ); var nextInstance = nextHydratableInstance; - if (!nextInstance || !tryHydrateInstance(fiber, nextInstance)) { + if ( + !nextInstance || + !tryHydrateInstance(fiber, nextInstance, currentHostContext) + ) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8443,7 +9154,7 @@ if (__DEV__) { if (!nextInstance || !tryHydrateText(fiber, nextInstance)) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8458,7 +9169,7 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); throwOnHydrationMismatch(); } } @@ -8493,25 +9204,6 @@ if (__DEV__) { function prepareToHydrateHostInstance(fiber, hostContext) { var instance = fiber.stateNode; - - { - var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV; - - if (shouldWarnIfMismatchDev) { - var differences = diffHydratedPropsForDevWarnings( - instance, - fiber.type, - fiber.memoizedProps, - hostContext - ); - - if (differences !== null) { - var diffNode = buildHydrationDiffNode(fiber); - diffNode.serverProps = differences; - } - } - } - var didHydrate = hydrateInstance( instance, fiber.type, @@ -8546,7 +9238,7 @@ if (__DEV__) { ); if (difference !== null) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = difference; } } @@ -8568,7 +9260,7 @@ if (__DEV__) { ); if (_difference !== null) { - var _diffNode = buildHydrationDiffNode(fiber); + var _diffNode = buildHydrationDiffNode(fiber, 0); _diffNode.serverProps = _difference; } @@ -8704,11 +9396,18 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; while (nextInstance) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); var description = describeHydratableInstanceForDevWarnings(nextInstance); diffNode.serverTail.push(description); - nextInstance = getNextHydratableSibling(nextInstance); + + if (description.type === "Suspense") { + var suspenseInstance = nextInstance; + nextInstance = + getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance); + } else { + nextInstance = getNextHydratableSibling(nextInstance); + } } } } @@ -8749,7 +9448,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + @@ -35604,7 +36303,7 @@ if (__DEV__) { return root; } - var ReactVersion = "19.0.0-www-classic-257063d3"; + var ReactVersion = "19.0.0-www-classic-441ec386"; function createPortal$1( children, @@ -40284,7 +40983,7 @@ if (__DEV__) { ) { { attributeNames.forEach(function (attributeName) { - serverDifferences[attributeName] = + serverDifferences[getPropNameFromAttributeName(attributeName)] = attributeName === "style" ? getStylesObjectFromElement(domElement) : domElement.getAttribute(attributeName); @@ -42123,13 +42822,27 @@ if (__DEV__) { } } + function getPropNameFromAttributeName(attrName) { + switch (attrName) { + case "class": + return "className"; + + case "for": + return "htmlFor"; + // TODO: The rest of the aliases. + + default: + return attrName; + } + } + function getPropsFromElement(domElement) { var serverDifferences = {}; var attributes = domElement.attributes; for (var i = 0; i < attributes.length; i++) { var attr = attributes[i]; - serverDifferences[attr.name] = + serverDifferences[getPropNameFromAttributeName(attr.name)] = attr.name.toLowerCase() === "style" ? getStylesObjectFromElement(domElement) : attr.value; diff --git a/compiled/facebook-www/ReactDOM-dev.modern.js b/compiled/facebook-www/ReactDOM-dev.modern.js index 16499440f13a6..2a48d590cdeb1 100644 --- a/compiled/facebook-www/ReactDOM-dev.modern.js +++ b/compiled/facebook-www/ReactDOM-dev.modern.js @@ -8109,8 +8109,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } // This may have been an insertion or a hydration. @@ -8127,7 +8782,7 @@ if (__DEV__) { var hydrationErrors = null; var rootOrSingletonContext = false; // Builds a common ancestor tree from the root down for collecting diffs. - function buildHydrationDiffNode(fiber) { + function buildHydrationDiffNode(fiber, distanceFromLeaf) { if (fiber.return === null) { // We're at the root. if (hydrationDiffRootDEV === null) { @@ -8135,18 +8790,24 @@ if (__DEV__) { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; } else if (hydrationDiffRootDEV.fiber !== fiber) { throw new Error( "Saw multiple hydration diff roots in a pass. This is a bug in React." ); + } else if (hydrationDiffRootDEV.distanceFromLeaf > distanceFromLeaf) { + hydrationDiffRootDEV.distanceFromLeaf = distanceFromLeaf; } return hydrationDiffRootDEV; } - var siblings = buildHydrationDiffNode(fiber.return).children; // The same node may already exist in the parent. Since we currently always render depth first + var siblings = buildHydrationDiffNode( + fiber.return, + distanceFromLeaf + 1 + ).children; // The same node may already exist in the parent. Since we currently always render depth first // and rerender if we suspend or terminate early, if a shared ancestor was added we should still // be inside of that shared ancestor which means it was the last one to be added. If this changes // we may have to scan the whole set. @@ -8155,14 +8816,21 @@ if (__DEV__) { siblings.length > 0 && siblings[siblings.length - 1].fiber === fiber ) { - return siblings[siblings.length - 1]; + var existing = siblings[siblings.length - 1]; + + if (existing.distanceFromLeaf > distanceFromLeaf) { + existing.distanceFromLeaf = distanceFromLeaf; + } + + return existing; } var newNode = { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; siblings.push(newNode); return newNode; @@ -8228,7 +8896,7 @@ if (__DEV__) { } } - function warnNonHydratedInstance(fiber) { + function warnNonHydratedInstance(fiber, rejectedCandidate) { { if (didSuspendOrErrorDEV) { // Inside a boundary that already suspended. We're currently rendering the @@ -8237,13 +8905,19 @@ if (__DEV__) { return; } // Add this fiber to the diff tree. - var diffNode = buildHydrationDiffNode(fiber); // We use null as a signal that there was no node to match. + var diffNode = buildHydrationDiffNode(fiber, 0); // We use null as a signal that there was no node to match. diffNode.serverProps = null; + + if (rejectedCandidate !== null) { + var description = + describeHydratableInstanceForDevWarnings(rejectedCandidate); + diffNode.serverTail.push(description); + } } } - function tryHydrateInstance(fiber, nextInstance) { + function tryHydrateInstance(fiber, nextInstance, hostContext) { // fiber is a HostComponent Fiber var instance = canHydrateInstance( nextInstance, @@ -8254,6 +8928,23 @@ if (__DEV__) { if (instance !== null) { fiber.stateNode = instance; + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + hostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; nextHydratableInstance = getFirstHydratableChild(instance); rootOrSingletonContext = false; @@ -8325,7 +9016,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - diff = describeDiff(); + diff = describeDiff(diffRoot); } } @@ -8360,6 +9051,23 @@ if (__DEV__) { currentHostContext, false )); + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + currentHostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; rootOrSingletonContext = true; nextHydratableInstance = getFirstHydratableChild(instance); @@ -8379,9 +9087,12 @@ if (__DEV__) { ); var nextInstance = nextHydratableInstance; - if (!nextInstance || !tryHydrateInstance(fiber, nextInstance)) { + if ( + !nextInstance || + !tryHydrateInstance(fiber, nextInstance, currentHostContext) + ) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8405,7 +9116,7 @@ if (__DEV__) { if (!nextInstance || !tryHydrateText(fiber, nextInstance)) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8420,7 +9131,7 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); throwOnHydrationMismatch(); } } @@ -8455,25 +9166,6 @@ if (__DEV__) { function prepareToHydrateHostInstance(fiber, hostContext) { var instance = fiber.stateNode; - - { - var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV; - - if (shouldWarnIfMismatchDev) { - var differences = diffHydratedPropsForDevWarnings( - instance, - fiber.type, - fiber.memoizedProps, - hostContext - ); - - if (differences !== null) { - var diffNode = buildHydrationDiffNode(fiber); - diffNode.serverProps = differences; - } - } - } - var didHydrate = hydrateInstance( instance, fiber.type, @@ -8508,7 +9200,7 @@ if (__DEV__) { ); if (difference !== null) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = difference; } } @@ -8530,7 +9222,7 @@ if (__DEV__) { ); if (_difference !== null) { - var _diffNode = buildHydrationDiffNode(fiber); + var _diffNode = buildHydrationDiffNode(fiber, 0); _diffNode.serverProps = _difference; } @@ -8666,11 +9358,18 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; while (nextInstance) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); var description = describeHydratableInstanceForDevWarnings(nextInstance); diffNode.serverTail.push(description); - nextInstance = getNextHydratableSibling(nextInstance); + + if (description.type === "Suspense") { + var suspenseInstance = nextInstance; + nextInstance = + getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance); + } else { + nextInstance = getNextHydratableSibling(nextInstance); + } } } } @@ -8711,7 +9410,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + @@ -35451,7 +36150,7 @@ if (__DEV__) { return root; } - var ReactVersion = "19.0.0-www-modern-e485b93c"; + var ReactVersion = "19.0.0-www-modern-3cd5f1d2"; function createPortal$1( children, @@ -40937,7 +41636,7 @@ if (__DEV__) { ) { { attributeNames.forEach(function (attributeName) { - serverDifferences[attributeName] = + serverDifferences[getPropNameFromAttributeName(attributeName)] = attributeName === "style" ? getStylesObjectFromElement(domElement) : domElement.getAttribute(attributeName); @@ -42773,13 +43472,27 @@ if (__DEV__) { } } + function getPropNameFromAttributeName(attrName) { + switch (attrName) { + case "class": + return "className"; + + case "for": + return "htmlFor"; + // TODO: The rest of the aliases. + + default: + return attrName; + } + } + function getPropsFromElement(domElement) { var serverDifferences = {}; var attributes = domElement.attributes; for (var i = 0; i < attributes.length; i++) { var attr = attributes[i]; - serverDifferences[attr.name] = + serverDifferences[getPropNameFromAttributeName(attr.name)] = attr.name.toLowerCase() === "style" ? getStylesObjectFromElement(domElement) : attr.value; diff --git a/compiled/facebook-www/ReactDOMTesting-dev.classic.js b/compiled/facebook-www/ReactDOMTesting-dev.classic.js index 13013dbd0f19f..fc86ddb7b1bcd 100644 --- a/compiled/facebook-www/ReactDOMTesting-dev.classic.js +++ b/compiled/facebook-www/ReactDOMTesting-dev.classic.js @@ -8284,8 +8284,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } // This may have been an insertion or a hydration. @@ -8302,7 +8957,7 @@ if (__DEV__) { var hydrationErrors = null; var rootOrSingletonContext = false; // Builds a common ancestor tree from the root down for collecting diffs. - function buildHydrationDiffNode(fiber) { + function buildHydrationDiffNode(fiber, distanceFromLeaf) { if (fiber.return === null) { // We're at the root. if (hydrationDiffRootDEV === null) { @@ -8310,18 +8965,24 @@ if (__DEV__) { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; } else if (hydrationDiffRootDEV.fiber !== fiber) { throw new Error( "Saw multiple hydration diff roots in a pass. This is a bug in React." ); + } else if (hydrationDiffRootDEV.distanceFromLeaf > distanceFromLeaf) { + hydrationDiffRootDEV.distanceFromLeaf = distanceFromLeaf; } return hydrationDiffRootDEV; } - var siblings = buildHydrationDiffNode(fiber.return).children; // The same node may already exist in the parent. Since we currently always render depth first + var siblings = buildHydrationDiffNode( + fiber.return, + distanceFromLeaf + 1 + ).children; // The same node may already exist in the parent. Since we currently always render depth first // and rerender if we suspend or terminate early, if a shared ancestor was added we should still // be inside of that shared ancestor which means it was the last one to be added. If this changes // we may have to scan the whole set. @@ -8330,14 +8991,21 @@ if (__DEV__) { siblings.length > 0 && siblings[siblings.length - 1].fiber === fiber ) { - return siblings[siblings.length - 1]; + var existing = siblings[siblings.length - 1]; + + if (existing.distanceFromLeaf > distanceFromLeaf) { + existing.distanceFromLeaf = distanceFromLeaf; + } + + return existing; } var newNode = { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; siblings.push(newNode); return newNode; @@ -8403,7 +9071,7 @@ if (__DEV__) { } } - function warnNonHydratedInstance(fiber) { + function warnNonHydratedInstance(fiber, rejectedCandidate) { { if (didSuspendOrErrorDEV) { // Inside a boundary that already suspended. We're currently rendering the @@ -8412,13 +9080,19 @@ if (__DEV__) { return; } // Add this fiber to the diff tree. - var diffNode = buildHydrationDiffNode(fiber); // We use null as a signal that there was no node to match. + var diffNode = buildHydrationDiffNode(fiber, 0); // We use null as a signal that there was no node to match. diffNode.serverProps = null; + + if (rejectedCandidate !== null) { + var description = + describeHydratableInstanceForDevWarnings(rejectedCandidate); + diffNode.serverTail.push(description); + } } } - function tryHydrateInstance(fiber, nextInstance) { + function tryHydrateInstance(fiber, nextInstance, hostContext) { // fiber is a HostComponent Fiber var instance = canHydrateInstance( nextInstance, @@ -8429,6 +9103,23 @@ if (__DEV__) { if (instance !== null) { fiber.stateNode = instance; + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + hostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; nextHydratableInstance = getFirstHydratableChild(instance); rootOrSingletonContext = false; @@ -8500,7 +9191,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - diff = describeDiff(); + diff = describeDiff(diffRoot); } } @@ -8535,6 +9226,23 @@ if (__DEV__) { currentHostContext, false )); + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + currentHostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; rootOrSingletonContext = true; nextHydratableInstance = getFirstHydratableChild(instance); @@ -8554,9 +9262,12 @@ if (__DEV__) { ); var nextInstance = nextHydratableInstance; - if (!nextInstance || !tryHydrateInstance(fiber, nextInstance)) { + if ( + !nextInstance || + !tryHydrateInstance(fiber, nextInstance, currentHostContext) + ) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8580,7 +9291,7 @@ if (__DEV__) { if (!nextInstance || !tryHydrateText(fiber, nextInstance)) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8595,7 +9306,7 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); throwOnHydrationMismatch(); } } @@ -8630,25 +9341,6 @@ if (__DEV__) { function prepareToHydrateHostInstance(fiber, hostContext) { var instance = fiber.stateNode; - - { - var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV; - - if (shouldWarnIfMismatchDev) { - var differences = diffHydratedPropsForDevWarnings( - instance, - fiber.type, - fiber.memoizedProps, - hostContext - ); - - if (differences !== null) { - var diffNode = buildHydrationDiffNode(fiber); - diffNode.serverProps = differences; - } - } - } - var didHydrate = hydrateInstance( instance, fiber.type, @@ -8683,7 +9375,7 @@ if (__DEV__) { ); if (difference !== null) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = difference; } } @@ -8705,7 +9397,7 @@ if (__DEV__) { ); if (_difference !== null) { - var _diffNode = buildHydrationDiffNode(fiber); + var _diffNode = buildHydrationDiffNode(fiber, 0); _diffNode.serverProps = _difference; } @@ -8841,11 +9533,18 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; while (nextInstance) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); var description = describeHydratableInstanceForDevWarnings(nextInstance); diffNode.serverTail.push(description); - nextInstance = getNextHydratableSibling(nextInstance); + + if (description.type === "Suspense") { + var suspenseInstance = nextInstance; + nextInstance = + getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance); + } else { + nextInstance = getNextHydratableSibling(nextInstance); + } } } } @@ -8886,7 +9585,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + @@ -36228,7 +36927,7 @@ if (__DEV__) { return root; } - var ReactVersion = "19.0.0-www-classic-f6343974"; + var ReactVersion = "19.0.0-www-classic-009e5152"; function createPortal$1( children, @@ -40908,7 +41607,7 @@ if (__DEV__) { ) { { attributeNames.forEach(function (attributeName) { - serverDifferences[attributeName] = + serverDifferences[getPropNameFromAttributeName(attributeName)] = attributeName === "style" ? getStylesObjectFromElement(domElement) : domElement.getAttribute(attributeName); @@ -42747,13 +43446,27 @@ if (__DEV__) { } } + function getPropNameFromAttributeName(attrName) { + switch (attrName) { + case "class": + return "className"; + + case "for": + return "htmlFor"; + // TODO: The rest of the aliases. + + default: + return attrName; + } + } + function getPropsFromElement(domElement) { var serverDifferences = {}; var attributes = domElement.attributes; for (var i = 0; i < attributes.length; i++) { var attr = attributes[i]; - serverDifferences[attr.name] = + serverDifferences[getPropNameFromAttributeName(attr.name)] = attr.name.toLowerCase() === "style" ? getStylesObjectFromElement(domElement) : attr.value; diff --git a/compiled/facebook-www/ReactDOMTesting-dev.modern.js b/compiled/facebook-www/ReactDOMTesting-dev.modern.js index 3338591a5f503..db4b6ed9e633b 100644 --- a/compiled/facebook-www/ReactDOMTesting-dev.modern.js +++ b/compiled/facebook-www/ReactDOMTesting-dev.modern.js @@ -8246,8 +8246,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } // This may have been an insertion or a hydration. @@ -8264,7 +8919,7 @@ if (__DEV__) { var hydrationErrors = null; var rootOrSingletonContext = false; // Builds a common ancestor tree from the root down for collecting diffs. - function buildHydrationDiffNode(fiber) { + function buildHydrationDiffNode(fiber, distanceFromLeaf) { if (fiber.return === null) { // We're at the root. if (hydrationDiffRootDEV === null) { @@ -8272,18 +8927,24 @@ if (__DEV__) { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; } else if (hydrationDiffRootDEV.fiber !== fiber) { throw new Error( "Saw multiple hydration diff roots in a pass. This is a bug in React." ); + } else if (hydrationDiffRootDEV.distanceFromLeaf > distanceFromLeaf) { + hydrationDiffRootDEV.distanceFromLeaf = distanceFromLeaf; } return hydrationDiffRootDEV; } - var siblings = buildHydrationDiffNode(fiber.return).children; // The same node may already exist in the parent. Since we currently always render depth first + var siblings = buildHydrationDiffNode( + fiber.return, + distanceFromLeaf + 1 + ).children; // The same node may already exist in the parent. Since we currently always render depth first // and rerender if we suspend or terminate early, if a shared ancestor was added we should still // be inside of that shared ancestor which means it was the last one to be added. If this changes // we may have to scan the whole set. @@ -8292,14 +8953,21 @@ if (__DEV__) { siblings.length > 0 && siblings[siblings.length - 1].fiber === fiber ) { - return siblings[siblings.length - 1]; + var existing = siblings[siblings.length - 1]; + + if (existing.distanceFromLeaf > distanceFromLeaf) { + existing.distanceFromLeaf = distanceFromLeaf; + } + + return existing; } var newNode = { fiber: fiber, children: [], serverProps: undefined, - serverTail: [] + serverTail: [], + distanceFromLeaf: distanceFromLeaf }; siblings.push(newNode); return newNode; @@ -8365,7 +9033,7 @@ if (__DEV__) { } } - function warnNonHydratedInstance(fiber) { + function warnNonHydratedInstance(fiber, rejectedCandidate) { { if (didSuspendOrErrorDEV) { // Inside a boundary that already suspended. We're currently rendering the @@ -8374,13 +9042,19 @@ if (__DEV__) { return; } // Add this fiber to the diff tree. - var diffNode = buildHydrationDiffNode(fiber); // We use null as a signal that there was no node to match. + var diffNode = buildHydrationDiffNode(fiber, 0); // We use null as a signal that there was no node to match. diffNode.serverProps = null; + + if (rejectedCandidate !== null) { + var description = + describeHydratableInstanceForDevWarnings(rejectedCandidate); + diffNode.serverTail.push(description); + } } } - function tryHydrateInstance(fiber, nextInstance) { + function tryHydrateInstance(fiber, nextInstance, hostContext) { // fiber is a HostComponent Fiber var instance = canHydrateInstance( nextInstance, @@ -8391,6 +9065,23 @@ if (__DEV__) { if (instance !== null) { fiber.stateNode = instance; + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + hostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; nextHydratableInstance = getFirstHydratableChild(instance); rootOrSingletonContext = false; @@ -8462,7 +9153,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - diff = describeDiff(); + diff = describeDiff(diffRoot); } } @@ -8497,6 +9188,23 @@ if (__DEV__) { currentHostContext, false )); + + { + if (!didSuspendOrErrorDEV) { + var differences = diffHydratedPropsForDevWarnings( + instance, + fiber.type, + fiber.pendingProps, + currentHostContext + ); + + if (differences !== null) { + var diffNode = buildHydrationDiffNode(fiber, 0); + diffNode.serverProps = differences; + } + } + } + hydrationParentFiber = fiber; rootOrSingletonContext = true; nextHydratableInstance = getFirstHydratableChild(instance); @@ -8516,9 +9224,12 @@ if (__DEV__) { ); var nextInstance = nextHydratableInstance; - if (!nextInstance || !tryHydrateInstance(fiber, nextInstance)) { + if ( + !nextInstance || + !tryHydrateInstance(fiber, nextInstance, currentHostContext) + ) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8542,7 +9253,7 @@ if (__DEV__) { if (!nextInstance || !tryHydrateText(fiber, nextInstance)) { if (shouldKeepWarning) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); } throwOnHydrationMismatch(); @@ -8557,7 +9268,7 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) { - warnNonHydratedInstance(fiber); + warnNonHydratedInstance(fiber, nextInstance); throwOnHydrationMismatch(); } } @@ -8592,25 +9303,6 @@ if (__DEV__) { function prepareToHydrateHostInstance(fiber, hostContext) { var instance = fiber.stateNode; - - { - var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV; - - if (shouldWarnIfMismatchDev) { - var differences = diffHydratedPropsForDevWarnings( - instance, - fiber.type, - fiber.memoizedProps, - hostContext - ); - - if (differences !== null) { - var diffNode = buildHydrationDiffNode(fiber); - diffNode.serverProps = differences; - } - } - } - var didHydrate = hydrateInstance( instance, fiber.type, @@ -8645,7 +9337,7 @@ if (__DEV__) { ); if (difference !== null) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = difference; } } @@ -8667,7 +9359,7 @@ if (__DEV__) { ); if (_difference !== null) { - var _diffNode = buildHydrationDiffNode(fiber); + var _diffNode = buildHydrationDiffNode(fiber, 0); _diffNode.serverProps = _difference; } @@ -8803,11 +9495,18 @@ if (__DEV__) { var nextInstance = nextHydratableInstance; while (nextInstance) { - var diffNode = buildHydrationDiffNode(fiber); + var diffNode = buildHydrationDiffNode(fiber, 0); var description = describeHydratableInstanceForDevWarnings(nextInstance); diffNode.serverTail.push(description); - nextInstance = getNextHydratableSibling(nextInstance); + + if (description.type === "Suspense") { + var suspenseInstance = nextInstance; + nextInstance = + getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance); + } else { + nextInstance = getNextHydratableSibling(nextInstance); + } } } } @@ -8848,7 +9547,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + @@ -36075,7 +36774,7 @@ if (__DEV__) { return root; } - var ReactVersion = "19.0.0-www-modern-31c2f9f1"; + var ReactVersion = "19.0.0-www-modern-ee915528"; function createPortal$1( children, @@ -41561,7 +42260,7 @@ if (__DEV__) { ) { { attributeNames.forEach(function (attributeName) { - serverDifferences[attributeName] = + serverDifferences[getPropNameFromAttributeName(attributeName)] = attributeName === "style" ? getStylesObjectFromElement(domElement) : domElement.getAttribute(attributeName); @@ -43397,13 +44096,27 @@ if (__DEV__) { } } + function getPropNameFromAttributeName(attrName) { + switch (attrName) { + case "class": + return "className"; + + case "for": + return "htmlFor"; + // TODO: The rest of the aliases. + + default: + return attrName; + } + } + function getPropsFromElement(domElement) { var serverDifferences = {}; var attributes = domElement.attributes; for (var i = 0; i < attributes.length; i++) { var attr = attributes[i]; - serverDifferences[attr.name] = + serverDifferences[getPropNameFromAttributeName(attr.name)] = attr.name.toLowerCase() === "style" ? getStylesObjectFromElement(domElement) : attr.value; diff --git a/compiled/facebook-www/ReactTestRenderer-dev.classic.js b/compiled/facebook-www/ReactTestRenderer-dev.classic.js index 20fc135ff747b..2bd47dc1bb91a 100644 --- a/compiled/facebook-www/ReactTestRenderer-dev.classic.js +++ b/compiled/facebook-www/ReactTestRenderer-dev.classic.js @@ -2688,8 +2688,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } var isHydrating = false; // This flag allows for warning supression when we expect there to be mismatches @@ -2760,7 +3415,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + @@ -26120,7 +26775,7 @@ if (__DEV__) { return root; } - var ReactVersion = "19.0.0-www-classic-52b71d6a"; + var ReactVersion = "19.0.0-www-classic-b112ab1e"; // Might add PROFILE later. diff --git a/compiled/facebook-www/ReactTestRenderer-dev.modern.js b/compiled/facebook-www/ReactTestRenderer-dev.modern.js index e9bfb48f940f0..3405783180768 100644 --- a/compiled/facebook-www/ReactTestRenderer-dev.modern.js +++ b/compiled/facebook-www/ReactTestRenderer-dev.modern.js @@ -2688,8 +2688,663 @@ if (__DEV__) { } } + var maxRowLength = 120; + var idealDepth = 15; + + function findNotableNode(node, indent) { + if ( + node.serverProps === undefined && + node.serverTail.length === 0 && + node.children.length === 1 && + node.distanceFromLeaf > 3 && + node.distanceFromLeaf > idealDepth - indent + ) { + // This is not an interesting node for contextual purposes so we can skip it. + var child = node.children[0]; + return findNotableNode(child, indent); + } + + return node; + } + + function indentation(indent) { + return " " + " ".repeat(indent); + } + + function added(indent) { + return "+ " + " ".repeat(indent); + } + + function removed(indent) { + return "- " + " ".repeat(indent); + } + + function describeFiberType(fiber) { + switch (fiber.tag) { + case HostHoistable: + case HostSingleton: + case HostComponent: + return fiber.type; + + case LazyComponent: + return "Lazy"; + + case SuspenseComponent: + return "Suspense"; + + case SuspenseListComponent: + return "SuspenseList"; + + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + var fn = fiber.type; + return fn.displayName || fn.name || null; + + case ForwardRef: + var render = fiber.type.render; + return render.displayName || render.name || null; + + case ClassComponent: + var ctr = fiber.type; + return ctr.displayName || ctr.name || null; + + default: + // Skip + return null; + } + } + + var needsEscaping = /["'&<>\n\t]/; + + function describeTextNode(content, maxLength) { + if (needsEscaping.test(content)) { + var encoded = JSON.stringify(content); + + if (encoded.length > maxLength - 2) { + if (maxLength < 8) { + return '{"..."}'; + } + + return "{" + encoded.slice(0, maxLength - 7) + '..."}'; + } + + return "{" + encoded + "}"; + } else { + if (content.length > maxLength) { + if (maxLength < 5) { + return '{"..."}'; + } + + return content.slice(0, maxLength - 3) + "..."; + } + + return content; + } + } + + function describeTextDiff(clientText, serverProps, indent) { + var maxLength = maxRowLength - indent * 2; + + if (serverProps === null) { + return added(indent) + describeTextNode(clientText, maxLength) + "\n"; + } else if (typeof serverProps === "string") { + var serverText = serverProps; + var firstDiff = 0; + + for ( + ; + firstDiff < serverText.length && firstDiff < clientText.length; + firstDiff++ + ) { + if ( + serverText.charCodeAt(firstDiff) !== + clientText.charCodeAt(firstDiff) + ) { + break; + } + } + + if (firstDiff > maxLength - 8 && firstDiff > 10) { + // The first difference between the two strings would be cut off, so cut off in + // the beginning instead. + clientText = "..." + clientText.slice(firstDiff - 8); + serverText = "..." + serverText.slice(firstDiff - 8); + } + + return ( + added(indent) + + describeTextNode(clientText, maxLength) + + "\n" + + removed(indent) + + describeTextNode(serverText, maxLength) + + "\n" + ); + } else { + return ( + indentation(indent) + describeTextNode(clientText, maxLength) + "\n" + ); + } + } + + function objectName(object) { + // $FlowFixMe[method-unbinding] + var name = Object.prototype.toString.call(object); + return name.replace(/^\[object (.*)\]$/, function (m, p0) { + return p0; + }); + } + + function describeValue(value, maxLength) { + switch (typeof value) { + case "string": { + var encoded = JSON.stringify(value); + + if (encoded.length > maxLength) { + if (maxLength < 5) { + return '"..."'; + } + + return encoded.slice(0, maxLength - 4) + '..."'; + } + + return encoded; + } + + case "object": { + if (value === null) { + return "null"; + } + + if (isArray(value)) { + return "[...]"; + } + + if (value.$$typeof === REACT_ELEMENT_TYPE) { + var type = getComponentNameFromType(value.type); + return type ? "<" + type + ">" : "<...>"; + } + + var name = objectName(value); + + if (name === "Object") { + var properties = ""; + maxLength -= 2; + + for (var propName in value) { + if (!value.hasOwnProperty(propName)) { + continue; + } + + var jsonPropName = JSON.stringify(propName); + + if (jsonPropName !== '"' + propName + '"') { + propName = jsonPropName; + } + + maxLength -= propName.length - 2; + var propValue = describeValue( + value[propName], + maxLength < 15 ? maxLength : 15 + ); + maxLength -= propValue.length; + + if (maxLength < 0) { + properties += properties === "" ? "..." : ", ..."; + break; + } + + properties += + (properties === "" ? "" : ",") + propName + ":" + propValue; + } + + return "{" + properties + "}"; + } + + return name; + } + + case "function": { + var _name = value.displayName || value.name; + + return _name ? "function " + _name : "function"; + } + + default: + // eslint-disable-next-line react-internal/safe-string-coercion + return String(value); + } + } + + function describePropValue(value, maxLength) { + if (typeof value === "string" && !needsEscaping.test(value)) { + if (value.length > maxLength - 2) { + if (maxLength < 5) { + return '"..."'; + } + + return '"' + value.slice(0, maxLength - 5) + '..."'; + } + + return '"' + value + '"'; + } + + return "{" + describeValue(value, maxLength - 2) + "}"; + } + + function describeCollapsedElement(type, props, indent) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var maxLength = maxRowLength - indent * 2 - type.length - 2; + var content = ""; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var propValue = describePropValue(props[propName], 15); + maxLength -= propName.length + propValue.length + 2; + + if (maxLength < 0) { + content += " ..."; + break; + } + + content += " " + propName + "=" + propValue; + } + + return indentation(indent) + "<" + type + content + ">\n"; + } + + function describeExpandedElement(type, props, rowPrefix) { + // This function tries to fit the props into a single line for non-essential elements. + // We also ignore children because we're not going deeper. + var remainingRowLength = maxRowLength - rowPrefix.length - type.length; // We add the properties to a set so we can choose later whether we'll put it on one + // line or multiple lines. + + var properties = []; + + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (propName === "children") { + // Ignored. + continue; + } + + var maxLength = maxRowLength - rowPrefix.length - propName.length - 1; + var propValue = describePropValue(props[propName], maxLength); + remainingRowLength -= propName.length + propValue.length + 2; + properties.push(propName + "=" + propValue); + } + + if (properties.length === 0) { + return rowPrefix + "<" + type + ">\n"; + } else if (remainingRowLength > 0) { + // We can fit all on one row. + return rowPrefix + "<" + type + " " + properties.join(" ") + ">\n"; + } else { + // Split into one row per property: + return ( + rowPrefix + + "<" + + type + + "\n" + + rowPrefix + + " " + + properties.join("\n" + rowPrefix + " ") + + "\n" + + rowPrefix + + ">\n" + ); + } + } + + function describePropertiesDiff(clientObject, serverObject, indent) { + var properties = ""; + var remainingServerProperties = assign({}, serverObject); + + for (var propName in clientObject) { + if (!clientObject.hasOwnProperty(propName)) { + continue; + } + + delete remainingServerProperties[propName]; + var maxLength = maxRowLength - indent * 2 - propName.length - 2; + var clientValue = clientObject[propName]; + var clientPropValue = describeValue(clientValue, maxLength); + + if (serverObject.hasOwnProperty(propName)) { + var serverValue = serverObject[propName]; + var serverPropValue = describeValue(serverValue, maxLength); + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + properties += + removed(indent) + propName + ": " + serverPropValue + "\n"; + } else { + properties += + added(indent) + propName + ": " + clientPropValue + "\n"; + } + } + + for (var _propName in remainingServerProperties) { + if (!remainingServerProperties.hasOwnProperty(_propName)) { + continue; + } + + var _maxLength = maxRowLength - indent * 2 - _propName.length - 2; + + var _serverValue = remainingServerProperties[_propName]; + + var _serverPropValue = describeValue(_serverValue, _maxLength); + + properties += + removed(indent) + _propName + ": " + _serverPropValue + "\n"; + } + + return properties; + } + + function describeElementDiff(type, clientProps, serverProps, indent) { + var content = ""; // Maps any previously unmatched lower case server prop name to its full prop name + + var serverPropNames = new Map(); + + for (var propName in serverProps) { + if (!serverProps.hasOwnProperty(propName)) { + continue; + } + + serverPropNames.set(propName.toLowerCase(), propName); + } + + if (serverPropNames.size === 1 && serverPropNames.has("children")) { + content += describeExpandedElement( + type, + clientProps, + indentation(indent) + ); + } else { + for (var _propName2 in clientProps) { + if (!clientProps.hasOwnProperty(_propName2)) { + continue; + } + + if (_propName2 === "children") { + // Handled below. + continue; + } + + var maxLength = + maxRowLength - (indent + 1) * 2 - _propName2.length - 1; + var serverPropName = serverPropNames.get(_propName2.toLowerCase()); + + if (serverPropName !== undefined) { + serverPropNames.delete(_propName2.toLowerCase()); // There's a diff here. + + var clientValue = clientProps[_propName2]; + var serverValue = serverProps[serverPropName]; + var clientPropValue = describePropValue(clientValue, maxLength); + var serverPropValue = describePropValue(serverValue, maxLength); + + if ( + typeof clientValue === "object" && + clientValue !== null && + typeof serverValue === "object" && + serverValue !== null && + objectName(clientValue) === "Object" && + objectName(serverValue) === "Object" && // Only do the diff if the object has a lot of keys or was shortened. + (Object.keys(clientValue).length > 2 || + Object.keys(serverValue).length > 2 || + clientPropValue.indexOf("...") > -1 || + serverPropValue.indexOf("...") > -1) + ) { + // We're comparing two plain objects. We can diff the nested objects instead. + content += + indentation(indent + 1) + + _propName2 + + "={{\n" + + describePropertiesDiff(clientValue, serverValue, indent + 2) + + indentation(indent + 1) + + "}}\n"; + } else { + content += + added(indent + 1) + _propName2 + "=" + clientPropValue + "\n"; + content += + removed(indent + 1) + _propName2 + "=" + serverPropValue + "\n"; + } + } else { + // Considered equal. + content += + indentation(indent + 1) + + _propName2 + + "=" + + describePropValue(clientProps[_propName2], maxLength) + + "\n"; + } + } + + serverPropNames.forEach(function (propName) { + if (propName === "children") { + // Handled below. + return; + } + + var maxLength = maxRowLength - (indent + 1) * 2 - propName.length - 1; + content += + removed(indent + 1) + + propName + + "=" + + describePropValue(serverProps[propName], maxLength) + + "\n"; + }); + + if (content === "") { + // No properties + content = indentation(indent) + "<" + type + ">\n"; + } else { + // Had properties + content = + indentation(indent) + + "<" + + type + + "\n" + + content + + indentation(indent) + + ">\n"; + } + } + + var serverChildren = serverProps.children; + var clientChildren = clientProps.children; + + if ( + typeof serverChildren === "string" || + typeof serverChildren === "number" || + typeof serverChildren === "bigint" + ) { + // There's a diff of the children. + // $FlowFixMe[unsafe-addition] + var serverText = "" + serverChildren; + var clientText = ""; + + if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // $FlowFixMe[unsafe-addition] + clientText = "" + clientChildren; + } + + content += describeTextDiff(clientText, serverText, indent + 1); + } else if ( + typeof clientChildren === "string" || + typeof clientChildren === "number" || + typeof clientChildren === "bigint" + ) { + // The client has children but it's not considered a difference from the server. + // $FlowFixMe[unsafe-addition] + content += describeTextDiff("" + clientChildren, undefined, indent + 1); + } + + return content; + } + + function describeSiblingFiber(fiber, indent) { + var type = describeFiberType(fiber); + + if (type === null) { + // Skip this type of fiber. We currently treat this as a fragment + // so it's just part of the parent's children. + var flatContent = ""; + var childFiber = fiber.child; + + while (childFiber) { + flatContent += describeSiblingFiber(childFiber, indent); + childFiber = childFiber.sibling; + } + + return flatContent; + } + + return indentation(indent) + "<" + type + ">" + "\n"; + } + + function describeNode(node, indent) { + var skipToNode = findNotableNode(node, indent); + + if ( + skipToNode !== node && + (node.children.length !== 1 || node.children[0] !== skipToNode) + ) { + return ( + indentation(indent) + "...\n" + describeNode(skipToNode, indent + 1) + ); + } // Prefix with any server components for context + + var parentContent = ""; + var debugInfo = node.fiber._debugInfo; + + if (debugInfo) { + for (var i = 0; i < debugInfo.length; i++) { + var serverComponentName = debugInfo[i].name; + + if (typeof serverComponentName === "string") { + parentContent += + indentation(indent) + "<" + serverComponentName + ">" + "\n"; + indent++; + } + } + } // Self + + var selfContent = ""; // We use the pending props since we might be generating a diff before the complete phase + // when something throws. + + var clientProps = node.fiber.pendingProps; + + if (node.fiber.tag === HostText) { + // Text Node + selfContent = describeTextDiff(clientProps, node.serverProps, indent); + } else { + var type = describeFiberType(node.fiber); + + if (type !== null) { + // Element Node + if (node.serverProps === undefined) { + // Just a reference node for context. + selfContent = describeCollapsedElement(type, clientProps, indent); + indent++; + } else if (node.serverProps === null) { + selfContent = describeExpandedElement( + type, + clientProps, + added(indent) + ); // If this was an insertion we won't step down further. Any tail + // are considered siblings so we don't indent. + // TODO: Model this a little better. + } else if (typeof node.serverProps === "string") { + { + error( + "Should not have matched a non HostText fiber to a Text node. This is a bug in React." + ); + } + } else { + selfContent = describeElementDiff( + type, + clientProps, + node.serverProps, + indent + ); + indent++; + } + } + } // Compute children + + var childContent = ""; + var childFiber = node.fiber.child; + var diffIdx = 0; + + while (childFiber && diffIdx < node.children.length) { + var childNode = node.children[diffIdx]; + + if (childNode.fiber === childFiber) { + // This was a match in the diff. + childContent += describeNode(childNode, indent); + diffIdx++; + } else { + // This is an unrelated previous sibling. + childContent += describeSiblingFiber(childFiber, indent); + } + + childFiber = childFiber.sibling; + } + + if (childFiber && node.children.length > 0) { + // If we had any further siblings after the last mismatch, we can't be sure if it's + // actually a valid match since it might not have found a match. So we exclude next + // siblings to avoid confusion. + childContent += indentation(indent) + "..." + "\n"; + } // Deleted tail nodes + + var serverTail = node.serverTail; + + for (var _i = 0; _i < serverTail.length; _i++) { + var tailNode = serverTail[_i]; + + if (typeof tailNode === "string") { + // Removed text node + childContent += + removed(indent) + + describeTextNode(tailNode, maxRowLength - indent * 2) + + "\n"; + } else { + // Removed element + childContent += describeExpandedElement( + tailNode.type, + tailNode.props, + removed(indent) + ); + } + } + + return parentContent + selfContent + childContent; + } + function describeDiff(rootNode) { - return "\n"; + try { + return "\n\n" + describeNode(rootNode, 0); + } catch (x) { + return ""; + } } var isHydrating = false; // This flag allows for warning supression when we expect there to be mismatches @@ -2760,7 +3415,7 @@ if (__DEV__) { if (diffRoot !== null) { hydrationDiffRootDEV = null; - var diff = describeDiff(); + var diff = describeDiff(diffRoot); error( "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " + @@ -26120,7 +26775,7 @@ if (__DEV__) { return root; } - var ReactVersion = "19.0.0-www-modern-52b71d6a"; + var ReactVersion = "19.0.0-www-modern-b112ab1e"; // Might add PROFILE later. diff --git a/compiled/facebook-www/__test_utils__/ReactAllWarnings.js b/compiled/facebook-www/__test_utils__/ReactAllWarnings.js index af1ac9638f4fe..8875df1870495 100644 --- a/compiled/facebook-www/__test_utils__/ReactAllWarnings.js +++ b/compiled/facebook-www/__test_utils__/ReactAllWarnings.js @@ -267,6 +267,7 @@ export default [ "Select elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled select element and remove one of these props. More info: https://react.dev/link/controlled-components", "Setting defaultProps as an instance property on %s is not supported and will be ignored. Instead, define defaultProps as a static property on %s.", "Should have found matching lanes. This is a bug in React.", + "Should not have matched a non HostText fiber to a Text node. This is a bug in React.", "State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().", "String refs are no longer supported.", "Style property values shouldn't contain a semicolon. Try \"%s: %s\" instead.",