Skip to content

Commit

Permalink
feat: refactored hydration logging and related tests (#5086)
Browse files Browse the repository at this point in the history
* feat: refactored hydration logging, related test changes

* fix: remove console usage

* fix: stringify

* fix: reduce nodes to nodeName for browser parity

* fix: disable_static_content_optimization test exceptions, logging refactor

* fix: changes for bundle size reduction

* fix: remove redundant non-prod checks

* fix: irregular naming, type guard

* fix: review comments

* fix: typing for static element comparison

* fix: missing util

* fix: move pretty print classes to util

* fix: naming, attribute prettifier

* fix: rename logging function
  • Loading branch information
jhefferman-sfdc authored Jan 9, 2025
1 parent f080072 commit 47c46eb
Show file tree
Hide file tree
Showing 47 changed files with 316 additions and 270 deletions.
105 changes: 105 additions & 0 deletions packages/@lwc/engine-core/src/framework/hydration-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2024, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { ArrayPush, ArrayJoin, ArraySort, ArrayFrom, isNull, isUndefined } from '@lwc/shared';

import { assertNotProd } from './utils';

// Errors that occured during the hydration process
let hydrationErrors: Array<HydrationError> = [];

// These values are the ones from Node.nodeType (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType)
const enum EnvNodeTypes {
ELEMENT = 1,
TEXT = 3,
COMMENT = 8,
}

interface HydrationError {
type: string;
serverRendered: any;
clientExpected: any;
}

export type Classes = Omit<Set<string>, 'add'>;

/*
Prints attributes as null or "value"
*/
export function prettyPrintAttribute(attribute: string, value: any): string {
assertNotProd(); // this method should never leak to prod
return `${attribute}=${isNull(value) || isUndefined(value) ? value : `"${value}"`}`;
}

/*
Sorts and stringifies classes
*/
export function prettyPrintClasses(classes: Classes) {
assertNotProd(); // this method should never leak to prod
const value = JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(classes)), ' '));
return `class=${value}`;
}

/*
Hydration errors occur before the source node has been fully hydrated,
queue them so they can be logged later against the mounted node.
*/
export function queueHydrationError(type: string, serverRendered?: any, clientExpected?: any) {
assertNotProd(); // this method should never leak to prod
ArrayPush.call(hydrationErrors, { type, serverRendered, clientExpected });
}

/*
Flushes (logs) any queued errors after the source node has been mounted.
*/
export function flushHydrationErrors(source?: Node | null) {
assertNotProd(); // this method should never leak to prod
for (const hydrationError of hydrationErrors) {
logHydrationWarning(
`Hydration ${hydrationError.type} mismatch on:`,
source,
`\n- rendered on server:`,
hydrationError.serverRendered,
`\n- expected on client:`,
hydrationError.clientExpected || source
);
}
hydrationErrors = [];
}

export function isTypeElement(node?: Node): node is Element {
const isCorrectType = node?.nodeType === EnvNodeTypes.ELEMENT;
if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
queueHydrationError('node', node);
}
return isCorrectType;
}

export function isTypeText(node?: Node): node is Text {
const isCorrectType = node?.nodeType === EnvNodeTypes.TEXT;
if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
queueHydrationError('node', node);
}
return isCorrectType;
}

export function isTypeComment(node?: Node): node is Comment {
const isCorrectType = node?.nodeType === EnvNodeTypes.COMMENT;
if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
queueHydrationError('node', node);
}
return isCorrectType;
}

/*
logger.ts converts all args to a string, losing object referenences and has
legacy bloat which would have meant more pathing.
*/
export function logHydrationWarning(...args: any) {
assertNotProd(); // this method should never leak to prod
/* eslint-disable-next-line no-console */
console.warn('[LWC warn:', ...args);
}
Loading

0 comments on commit 47c46eb

Please sign in to comment.