From 1fc73fbd8fac475ccf6888696c84f19297b4bb7a Mon Sep 17 00:00:00 2001 From: sebmarkbage Date: Wed, 27 Mar 2024 00:08:21 +0000 Subject: [PATCH] Add Diffs to Hydration Warnings (#28512) Stacked on #28502. This builds on the mechanism in #28502 by adding a diff of everything we've collected so far to the thrown error or logged error. This isn't actually a longest common subsequence diff. This means that there are certain cases that can appear confusing such as a node being added/removed when it really would've appeared later in the list. In fact once a node mismatches, we abort rendering so we don't have the context of what would've been rendered. It's not quite right to use the result of the recovery render because it can use client-only code paths using useSyncExternalStore which would yield false differences. That's why diffing the HTML isn't quite right. I also present abstract components in the stack, these are presented with the client props and no diff since we don't have the props that were on the server. The lack of difference might be confusing but it's useful for context. The main thing that's data new here is that we're adding some siblings and props for context. Examples in the [snapshot commit](https://github.com/facebook/react/pull/28512/commits/e14532fd8df858a319d3eca687d83227209a498c). DiffTrain build for [2ec2aaea98588178525f83495669e11e96815a00](https://github.com/facebook/react/commit/2ec2aaea98588178525f83495669e11e96815a00) --- compiled/facebook-www/REVISION | 2 +- compiled/facebook-www/ReactART-dev.classic.js | 661 ++++++++++++++- compiled/facebook-www/ReactART-dev.modern.js | 661 ++++++++++++++- compiled/facebook-www/ReactDOM-dev.classic.js | 795 +++++++++++++++++- compiled/facebook-www/ReactDOM-dev.modern.js | 795 +++++++++++++++++- .../ReactDOMTesting-dev.classic.js | 795 +++++++++++++++++- .../ReactDOMTesting-dev.modern.js | 795 +++++++++++++++++- .../ReactTestRenderer-dev.classic.js | 661 ++++++++++++++- .../ReactTestRenderer-dev.modern.js | 661 ++++++++++++++- .../__test_utils__/ReactAllWarnings.js | 1 + 10 files changed, 5650 insertions(+), 177 deletions(-) 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.",