Skip to content

Commit

Permalink
fix: disable_static_content_optimization test exceptions, logging ref…
Browse files Browse the repository at this point in the history
…actor
  • Loading branch information
jhefferman-sfdc committed Dec 31, 2024
1 parent a4fabab commit e210d8f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 62 deletions.
129 changes: 81 additions & 48 deletions packages/@lwc/engine-core/src/framework/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,54 +82,86 @@ let hasMismatch = false;
// Errors queued during hydration process. Flushed after the node has been mounted.
let hydrationErrors: Array<HydrationError> = [];

enum HydrationErrorTypes {
Node = 'node',
Attribute = 'attribute',
InnerHTML = 'innerHTML',
Comment = 'comment',
TextContent = 'text content',
ChildNode = 'child node',
}

class HydrationError {
errorType: string;
type: HydrationErrorTypes;
serverRendered: any;
clientExpected: any;

constructor(errorType: string, serverRendered?: any, clientExpected?: any) {
this.errorType = errorType;
private constructor(type: HydrationErrorTypes, serverRendered?: any, clientExpected?: any) {
this.type = type;
this.serverRendered = serverRendered;
this.clientExpected = clientExpected;
}

log(context?: Node | null) {
public static create(
type: HydrationErrorTypes,
serverRendered?: any,
clientExpected?: any
): HydrationError {
return new HydrationError(type, serverRendered, clientExpected);
}

public static node(serverRendered?: any): HydrationError {
return new HydrationError(HydrationErrorTypes.Node, serverRendered);
}

public static attribute(
attributeName: string,
serverRendered?: any,
clientExpected?: any
): HydrationError {
return new HydrationError(
HydrationErrorTypes.Attribute,
`${attributeName}=${serverRendered}`,
`${attributeName}=${clientExpected}`
);
}

log(source?: Node | null) {
if (process.env.NODE_ENV !== 'production') {
logWarn(
`Hydration ${this.errorType} mismatch on:`,
context,
`Hydration ${this.type} mismatch on:`,
source,
`\n- rendered on server:`,
this.serverRendered,
`\n- expected on client:`,
this.clientExpected
this.clientExpected || source
);
}
}
}

class NodeHydrationError extends HydrationError {
constructor(serverRendered?: any) {
super('node', serverRendered);
}

log(context?: Node | null) {
this.clientExpected = context;
super.log(context);
function isTypeElement(node?: Node): node is Element {
const isCorrectType = node?.nodeType === EnvNodeTypes.ELEMENT;
if (!isCorrectType) {
queueHydrationError(HydrationError.node(node));
}
return isCorrectType;
}

class AttributeHydrationError extends HydrationError {
constructor(attribute: string, serverRendered?: any, clientExpected?: any) {
super('attribute', `${attribute}=${serverRendered}`, `${attribute}=${clientExpected}`);
function isTypeText(node?: Node): node is Text {
const isCorrectType = node?.nodeType === EnvNodeTypes.TEXT;
if (!isCorrectType) {
queueHydrationError(HydrationError.node(node));
}
return isCorrectType;
}

function isTypeElement(node?: Node): node is Element {
if (node?.nodeType !== EnvNodeTypes.ELEMENT) {
queueHydrationError(new NodeHydrationError(node));
return false;
function isTypeComment(node?: Node): node is Comment {
const isCorrectType = node?.nodeType === EnvNodeTypes.COMMENT;
if (!isCorrectType) {
queueHydrationError(HydrationError.node(node));
}
return true;
return isCorrectType;
}

/*
Expand All @@ -141,6 +173,10 @@ function logWarn(...args: any) {
console.warn('[LWC warn:', ...args);
}

function prettyPrint(set: Classes) {
return JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(set)), ' '));
}

export function hydrateRoot(vm: VM) {
hasMismatch = false;

Expand Down Expand Up @@ -238,7 +274,9 @@ function validateEqualTextNodeContent(
return true;
}

queueHydrationError(new HydrationError('text content', nodeValue, vnode.text));
queueHydrationError(
HydrationError.create(HydrationErrorTypes.TextContent, nodeValue, vnode.text)
);
return false;
}

Expand Down Expand Up @@ -296,8 +334,7 @@ function getValidationPredicate(
}

function hydrateText(node: Node, vnode: VText, renderer: RendererAPI): Node | null {
if (node?.nodeType !== EnvNodeTypes.TEXT) {
hydrationErrors.push(new NodeHydrationError(node));
if (!isTypeText(node)) {
return handleMismatch(node, vnode, renderer);
}
return updateTextContent(node, vnode, renderer);
Expand All @@ -319,24 +356,24 @@ function updateTextContent(
}

function hydrateComment(node: Node, vnode: VComment, renderer: RendererAPI): Node | null {
//if (!hasCorrectNodeType(vnode, node, EnvNodeTypes.COMMENT, renderer)) {
if (node?.nodeType !== EnvNodeTypes.COMMENT) {
hydrationErrors.push(new NodeHydrationError(node));
if (!isTypeComment(node)) {
return handleMismatch(node, vnode, renderer);
}
if (process.env.NODE_ENV !== 'production') {
const { getProperty } = renderer;
const nodeValue = getProperty(node, NODE_VALUE_PROP);

if (nodeValue !== vnode.text) {
queueHydrationError(new HydrationError('comment', nodeValue, vnode.text));
queueHydrationError(
HydrationError.create(HydrationErrorTypes.Comment, nodeValue, vnode.text)
);
}
}

const { setProperty } = renderer;
// We only set the `nodeValue` property here (on a comment), so we don't need
// to sanitize the content as HTML using `safelySetProperty`
setProperty(node as Element, NODE_VALUE_PROP, vnode.text ?? null);
setProperty(node, NODE_VALUE_PROP, vnode.text ?? null);
vnode.elm = node;

return node;
Expand Down Expand Up @@ -413,8 +450,8 @@ function hydrateElement(elm: Node, vnode: VElement, renderer: RendererAPI): Node
};
} else {
queueHydrationError(
new HydrationError(
'innerHTML',
HydrationError.create(
HydrationErrorTypes.InnerHTML,
unwrappedServerInnerHTML,
unwrappedClientInnerHTML
)
Expand Down Expand Up @@ -555,7 +592,9 @@ function hydrateChildren(
// We can't know exactly which node(s) caused the delta, but we can provide context (parent) and the mismatched sets
if (process.env.NODE_ENV !== 'production') {
const clientNodes = children.map((c) => c?.elm);
queueHydrationError(new HydrationError('child node', serverNodes, clientNodes));
queueHydrationError(
HydrationError.create(HydrationErrorTypes.ChildNode, serverNodes, clientNodes)
);
}
}
}
Expand Down Expand Up @@ -584,7 +623,7 @@ function isMatchingElement(
) {
const { getProperty } = renderer;
if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
queueHydrationError(new NodeHydrationError(elm));
queueHydrationError(HydrationError.node(elm));
return false;
}

Expand Down Expand Up @@ -641,7 +680,7 @@ function validateAttrs(
const elmAttrValue = getAttribute(elm, attrName);
if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
queueHydrationError(
new AttributeHydrationError(
HydrationError.attribute(
attrName,
isNull(elmAttrValue) ? elmAttrValue : `"${elmAttrValue}"`,
isNull(attrValue) ? attrValue : `"${attrValue}"`
Expand Down Expand Up @@ -732,11 +771,9 @@ function validateClassAttr(

const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);

if (process.env.NODE_ENV !== 'production' && !classesAreCompatible) {
const prettyPrint = (set: Classes) =>
JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(set)), ' '));
if (!classesAreCompatible) {
queueHydrationError(
new AttributeHydrationError('class', prettyPrint(elmClasses), prettyPrint(vnodeClasses))
HydrationError.attribute('class', prettyPrint(elmClasses), prettyPrint(vnodeClasses))
);
}

Expand Down Expand Up @@ -786,9 +823,7 @@ function validateStyleAttr(
}

if (!nodesAreCompatible) {
queueHydrationError(
new AttributeHydrationError('style', `"${elmStyle}"`, `"${vnodeStyle}"`)
);
queueHydrationError(HydrationError.attribute('style', `"${elmStyle}"`, `"${vnodeStyle}"`));
}

return nodesAreCompatible;
Expand All @@ -805,7 +840,7 @@ function areStaticNodesCompatible(
let isCompatibleElements = true;

if (getProperty(clientNode, 'tagName') !== getProperty(serverNode, 'tagName')) {
queueHydrationError(new NodeHydrationError(serverNode));
queueHydrationError(HydrationError.node(serverNode));
return false;
}

Expand All @@ -823,7 +858,7 @@ function areStaticNodesCompatible(
// partId === 0 will always refer to the root element, this is guaranteed by the compiler.
if (parts?.[0].partId !== 0) {
queueHydrationError(
new AttributeHydrationError(
HydrationError.attribute(
attrName,
`"${serverAttributeValue}"`,
`"${clientAttributeValue}"`
Expand Down Expand Up @@ -851,7 +886,6 @@ function haveCompatibleStaticParts(vnode: VStatic, renderer: RendererAPI) {
for (const part of parts) {
const { elm } = part;
if (isVStaticPartElement(part)) {
// !hasCorrectNodeType<Element>(vnode, elm!, EnvNodeTypes.ELEMENT, renderer)
if (!isTypeElement(elm)) {
return false;
}
Expand All @@ -873,8 +907,7 @@ function haveCompatibleStaticParts(vnode: VStatic, renderer: RendererAPI) {
}
} else {
// VStaticPartText
if (elm?.nodeType !== EnvNodeTypes.TEXT) {
queueHydrationError(new NodeHydrationError(elm));
if (!isTypeText(elm)) {
return false;
}
updateTextContent(elm, part as VStaticPartText, renderer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@ export default {
expect(p).toBe(snapshots.p);
expect(p.firstChild).toBe(snapshots.text);
expect(p.textContent).toBe('bye!');

TestUtils.expectConsoleCallsDev(consoleCalls, {
error: [],
warn: [
'Hydration text content mismatch on: P - rendered on server: hello! - expected on client: bye!',
],
});
if (process.env.DISABLE_STATIC_CONTENT_OPTIMIZATION) {
TestUtils.expectConsoleCallsDev(consoleCalls, {
error: [],
warn: [
'Hydration text content mismatch on: #text - rendered on server: hello! - expected on client: bye!',
],
});
} else {
TestUtils.expectConsoleCallsDev(consoleCalls, {
error: [],
warn: [
'Hydration text content mismatch on: P - rendered on server: hello! - expected on client: bye!',
],
});
}
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@ export default {

expect(hydratedSnapshot.text).not.toBe(snapshots.text);

TestUtils.expectConsoleCallsDev(consoleCalls, {
error: [],
warn: [
'Hydration child node mismatch on: UL - rendered on server: LI,LI,LI - expected on client: LI,,LI',
'Hydration completed with errors.',
],
});
if (process.env.DISABLE_STATIC_CONTENT_OPTIMIZATION) {
TestUtils.expectConsoleCallsDev(consoleCalls, {
error: [],
warn: [
'Hydration text content mismatch on: #text - rendered on server: blue - expected on client: green',
'Hydration child node mismatch on: UL - rendered on server: LI,LI,LI - expected on client: LI,,LI',
'Hydration completed with errors.',
],
});
} else {
TestUtils.expectConsoleCallsDev(consoleCalls, {
error: [],
warn: [
'Hydration child node mismatch on: UL - rendered on server: LI,LI,LI - expected on client: LI,,LI',
'Hydration completed with errors.',
],
});
}
},
};

0 comments on commit e210d8f

Please sign in to comment.