@lwc/engine-core 8.12.4 → 8.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ export type Classes = Omit<Set<string>, 'add'>;
2
+ export declare function prettyPrintAttribute(attribute: string, value: any): string;
3
+ export declare function prettyPrintClasses(classes: Classes): string;
4
+ export declare function queueHydrationError(type: string, serverRendered?: any, clientExpected?: any): void;
5
+ export declare function flushHydrationErrors(source?: Node | null): void;
6
+ export declare function isTypeElement(node?: Node): node is Element;
7
+ export declare function isTypeText(node?: Node): node is Text;
8
+ export declare function isTypeComment(node?: Node): node is Comment;
9
+ export declare function logHydrationWarning(...args: any): void;
10
+ //# sourceMappingURL=hydration-utils.d.ts.map
@@ -3,6 +3,7 @@ declare const sanitizedHtmlContentSymbol: unique symbol;
3
3
  export type SanitizedHtmlContent = {
4
4
  [sanitizedHtmlContentSymbol]: unknown;
5
5
  };
6
+ export declare function unwrapIfNecessary(object: any): any;
6
7
  /**
7
8
  * Wrap a pre-sanitized string designated for `.innerHTML` via `lwc:inner-html`
8
9
  * as an object with a Symbol that only we have access to.
@@ -19,12 +20,5 @@ export declare function createSanitizedHtmlContent(sanitizedString: unknown): Sa
19
20
  * @param value - value to set
20
21
  */
21
22
  export declare function safelySetProperty(setProperty: RendererAPI['setProperty'], elm: Element, key: string, value: any): void;
22
- /**
23
- * Given two objects (likely either a string or a SanitizedHtmlContent object), return true if their
24
- * string values are equivalent.
25
- * @param first
26
- * @param second
27
- */
28
- export declare function isSanitizedHtmlContentEqual(first: any, second: any): boolean;
29
23
  export {};
30
24
  //# sourceMappingURL=sanitized-html-content.d.ts.map
package/dist/index.cjs.js CHANGED
@@ -3977,15 +3977,6 @@ function safelySetProperty(setProperty, elm, key, value) {
3977
3977
  setProperty(elm, key, value);
3978
3978
  }
3979
3979
  }
3980
- /**
3981
- * Given two objects (likely either a string or a SanitizedHtmlContent object), return true if their
3982
- * string values are equivalent.
3983
- * @param first
3984
- * @param second
3985
- */
3986
- function isSanitizedHtmlContentEqual(first, second) {
3987
- return unwrapIfNecessary(first) === unwrapIfNecessary(second);
3988
- }
3989
3980
 
3990
3981
  /*
3991
3982
  * Copyright (c) 2018, salesforce.com, inc.
@@ -7538,6 +7529,78 @@ if (process.env.IS_BROWSER && isGlobalAriaPolyfillLoaded()) {
7538
7529
  }
7539
7530
  }
7540
7531
 
7532
+ /*
7533
+ * Copyright (c) 2024, Salesforce, Inc.
7534
+ * All rights reserved.
7535
+ * SPDX-License-Identifier: MIT
7536
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
7537
+ */
7538
+ // Errors that occured during the hydration process
7539
+ let hydrationErrors = [];
7540
+ /*
7541
+ Prints attributes as null or "value"
7542
+ */
7543
+ function prettyPrintAttribute(attribute, value) {
7544
+ assertNotProd(); // this method should never leak to prod
7545
+ return `${attribute}=${shared.isNull(value) || shared.isUndefined(value) ? value : `"${value}"`}`;
7546
+ }
7547
+ /*
7548
+ Sorts and stringifies classes
7549
+ */
7550
+ function prettyPrintClasses(classes) {
7551
+ assertNotProd(); // this method should never leak to prod
7552
+ const value = JSON.stringify(shared.ArrayJoin.call(shared.ArraySort.call(shared.ArrayFrom(classes)), ' '));
7553
+ return `class=${value}`;
7554
+ }
7555
+ /*
7556
+ Hydration errors occur before the source node has been fully hydrated,
7557
+ queue them so they can be logged later against the mounted node.
7558
+ */
7559
+ function queueHydrationError(type, serverRendered, clientExpected) {
7560
+ assertNotProd(); // this method should never leak to prod
7561
+ shared.ArrayPush.call(hydrationErrors, { type, serverRendered, clientExpected });
7562
+ }
7563
+ /*
7564
+ Flushes (logs) any queued errors after the source node has been mounted.
7565
+ */
7566
+ function flushHydrationErrors(source) {
7567
+ assertNotProd(); // this method should never leak to prod
7568
+ for (const hydrationError of hydrationErrors) {
7569
+ logHydrationWarning(`Hydration ${hydrationError.type} mismatch on:`, source, `\n- rendered on server:`, hydrationError.serverRendered, `\n- expected on client:`, hydrationError.clientExpected || source);
7570
+ }
7571
+ hydrationErrors = [];
7572
+ }
7573
+ function isTypeElement(node) {
7574
+ const isCorrectType = node?.nodeType === 1 /* EnvNodeTypes.ELEMENT */;
7575
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7576
+ queueHydrationError('node', node);
7577
+ }
7578
+ return isCorrectType;
7579
+ }
7580
+ function isTypeText(node) {
7581
+ const isCorrectType = node?.nodeType === 3 /* EnvNodeTypes.TEXT */;
7582
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7583
+ queueHydrationError('node', node);
7584
+ }
7585
+ return isCorrectType;
7586
+ }
7587
+ function isTypeComment(node) {
7588
+ const isCorrectType = node?.nodeType === 8 /* EnvNodeTypes.COMMENT */;
7589
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7590
+ queueHydrationError('node', node);
7591
+ }
7592
+ return isCorrectType;
7593
+ }
7594
+ /*
7595
+ logger.ts converts all args to a string, losing object referenences and has
7596
+ legacy bloat which would have meant more pathing.
7597
+ */
7598
+ function logHydrationWarning(...args) {
7599
+ assertNotProd(); // this method should never leak to prod
7600
+ /* eslint-disable-next-line no-console */
7601
+ console.warn('[LWC warn:', ...args);
7602
+ }
7603
+
7541
7604
  /*
7542
7605
  * Copyright (c) 2022, salesforce.com, inc.
7543
7606
  * All rights reserved.
@@ -7552,8 +7615,15 @@ function hydrateRoot(vm) {
7552
7615
  hasMismatch = false;
7553
7616
  runConnectedCallback(vm);
7554
7617
  hydrateVM(vm);
7555
- if (hasMismatch && process.env.NODE_ENV !== 'production') {
7556
- logWarn('Hydration completed with errors.', vm);
7618
+ if (process.env.NODE_ENV !== 'production') {
7619
+ /*
7620
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7621
+ Means the element in the console matches what is on the page and the highlighting works properly when you hover over the elements in the console.
7622
+ */
7623
+ flushHydrationErrors(vm.renderRoot);
7624
+ if (hasMismatch) {
7625
+ logHydrationWarning('Hydration completed with errors.');
7626
+ }
7557
7627
  }
7558
7628
  }
7559
7629
  function hydrateVM(vm) {
@@ -7591,21 +7661,25 @@ function hydrateNode(node, vnode, renderer) {
7591
7661
  hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer);
7592
7662
  break;
7593
7663
  }
7664
+ if (process.env.NODE_ENV !== 'production') {
7665
+ /*
7666
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7667
+ Means the element in the console matches what is on the page and the highlighting works properly when you hover over the elements in the console.
7668
+ */
7669
+ flushHydrationErrors(hydratedNode);
7670
+ }
7594
7671
  return renderer.nextSibling(hydratedNode);
7595
7672
  }
7596
7673
  const NODE_VALUE_PROP = 'nodeValue';
7597
- function textNodeContentsAreEqual(node, vnode, renderer) {
7674
+ function validateTextNodeEquality(node, vnode, renderer) {
7598
7675
  const { getProperty } = renderer;
7599
7676
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7600
- if (nodeValue === vnode.text) {
7601
- return true;
7602
- }
7603
- // Special case for empty text nodes these are serialized differently on the server
7604
- // See https://github.com/salesforce/lwc/pull/2656
7605
- if (nodeValue === '\u200D' && vnode.text === '') {
7606
- return true;
7677
+ if (nodeValue !== vnode.text &&
7678
+ // Special case for empty text nodes – these are serialized differently on the server
7679
+ // See https://github.com/salesforce/lwc/pull/2656
7680
+ (nodeValue !== '\u200D' || vnode.text !== '')) {
7681
+ queueHydrationError('text content', nodeValue, vnode.text);
7607
7682
  }
7608
- return false;
7609
7683
  }
7610
7684
  // The validationOptOut static property can be an array of attribute names.
7611
7685
  // Any attribute names specified in that array will not be validated, and the
@@ -7628,7 +7702,7 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7628
7702
  !shared.isUndefined(optOutStaticProp) &&
7629
7703
  !shared.isTrue(optOutStaticProp) &&
7630
7704
  !isValidArray) {
7631
- logWarn('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7705
+ logHydrationWarning('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7632
7706
  }
7633
7707
  return (attrName) => {
7634
7708
  // Component wants to opt out of all validation
@@ -7648,16 +7722,14 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7648
7722
  };
7649
7723
  }
7650
7724
  function hydrateText(node, vnode, renderer) {
7651
- if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7725
+ if (!isTypeText(node)) {
7652
7726
  return handleMismatch(node, vnode, renderer);
7653
7727
  }
7654
- return updateTextContent(node, vnode, vnode.owner, renderer);
7728
+ return updateTextContent(node, vnode, renderer);
7655
7729
  }
7656
- function updateTextContent(node, vnode, owner, renderer) {
7730
+ function updateTextContent(node, vnode, renderer) {
7657
7731
  if (process.env.NODE_ENV !== 'production') {
7658
- if (!textNodeContentsAreEqual(node, vnode, renderer)) {
7659
- logWarn('Hydration mismatch: text values do not match, will recover from the difference', owner);
7660
- }
7732
+ validateTextNodeEquality(node, vnode, renderer);
7661
7733
  }
7662
7734
  const { setText } = renderer;
7663
7735
  setText(node, vnode.text ?? null);
@@ -7665,14 +7737,14 @@ function updateTextContent(node, vnode, owner, renderer) {
7665
7737
  return node;
7666
7738
  }
7667
7739
  function hydrateComment(node, vnode, renderer) {
7668
- if (!hasCorrectNodeType(vnode, node, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
7740
+ if (!isTypeComment(node)) {
7669
7741
  return handleMismatch(node, vnode, renderer);
7670
7742
  }
7671
7743
  if (process.env.NODE_ENV !== 'production') {
7672
7744
  const { getProperty } = renderer;
7673
7745
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7674
7746
  if (nodeValue !== vnode.text) {
7675
- logWarn('Hydration mismatch: comment values do not match, will recover from the difference', vnode.owner);
7747
+ queueHydrationError('comment', nodeValue, vnode.text);
7676
7748
  }
7677
7749
  }
7678
7750
  const { setProperty } = renderer;
@@ -7683,11 +7755,12 @@ function hydrateComment(node, vnode, renderer) {
7683
7755
  return node;
7684
7756
  }
7685
7757
  function hydrateStaticElement(elm, vnode, renderer) {
7686
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7687
- !areCompatibleStaticNodes(vnode.fragment, elm, vnode, renderer)) {
7688
- return handleMismatch(elm, vnode, renderer);
7758
+ if (isTypeElement(elm) &&
7759
+ isTypeElement(vnode.fragment) &&
7760
+ areStaticElementsCompatible(vnode.fragment, elm, vnode, renderer)) {
7761
+ return hydrateStaticElementParts(elm, vnode, renderer);
7689
7762
  }
7690
- return hydrateStaticElementParts(elm, vnode, renderer);
7763
+ return handleMismatch(elm, vnode, renderer);
7691
7764
  }
7692
7765
  function hydrateStaticElementParts(elm, vnode, renderer) {
7693
7766
  const { parts } = vnode;
@@ -7710,8 +7783,7 @@ function hydrateFragment(elm, vnode, renderer) {
7710
7783
  return (vnode.elm = children[children.length - 1].elm);
7711
7784
  }
7712
7785
  function hydrateElement(elm, vnode, renderer) {
7713
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7714
- !isMatchingElement(vnode, elm, renderer)) {
7786
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer)) {
7715
7787
  return handleMismatch(elm, vnode, renderer);
7716
7788
  }
7717
7789
  vnode.elm = elm;
@@ -7724,17 +7796,17 @@ function hydrateElement(elm, vnode, renderer) {
7724
7796
  const { data: { props }, } = vnode;
7725
7797
  const { getProperty } = renderer;
7726
7798
  if (!shared.isUndefined(props) && !shared.isUndefined(props.innerHTML)) {
7727
- if (isSanitizedHtmlContentEqual(getProperty(elm, 'innerHTML'), props.innerHTML)) {
7799
+ const unwrappedServerInnerHTML = unwrapIfNecessary(getProperty(elm, 'innerHTML'));
7800
+ const unwrappedClientInnerHTML = unwrapIfNecessary(props.innerHTML);
7801
+ if (unwrappedServerInnerHTML === unwrappedClientInnerHTML) {
7728
7802
  // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization
7729
7803
  vnode.data = {
7730
7804
  ...vnode.data,
7731
7805
  props: cloneAndOmitKey(props, 'innerHTML'),
7732
7806
  };
7733
7807
  }
7734
- else {
7735
- if (process.env.NODE_ENV !== 'production') {
7736
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: innerHTML values do not match for element, will recover from the difference`, owner);
7737
- }
7808
+ else if (process.env.NODE_ENV !== 'production') {
7809
+ queueHydrationError('innerHTML', unwrappedServerInnerHTML, unwrappedClientInnerHTML);
7738
7810
  }
7739
7811
  }
7740
7812
  }
@@ -7757,8 +7829,7 @@ function hydrateCustomElement(elm, vnode, renderer) {
7757
7829
  //
7758
7830
  // Therefore, if validationOptOut is falsey or an array of strings, we need to
7759
7831
  // examine some or all of the custom element's attributes.
7760
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7761
- !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7832
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7762
7833
  return handleMismatch(elm, vnode, renderer);
7763
7834
  }
7764
7835
  const { sel, mode, ctor, owner } = vnode;
@@ -7794,9 +7865,13 @@ function hydrateChildren(node, children, parentNode, owner,
7794
7865
  // last node of the fragment. Hydration should not fail if a trailing sibling is
7795
7866
  // found in this case.
7796
7867
  expectAddlSiblings) {
7797
- let hasWarned = false;
7868
+ let mismatchedChildren = false;
7798
7869
  let nextNode = node;
7799
7870
  const { renderer } = owner;
7871
+ const { getChildNodes, cloneNode } = renderer;
7872
+ const serverNodes = process.env.NODE_ENV !== 'production'
7873
+ ? Array.from(getChildNodes(parentNode), (node) => cloneNode(node, true))
7874
+ : null;
7800
7875
  for (let i = 0; i < children.length; i++) {
7801
7876
  const childVnode = children[i];
7802
7877
  if (!shared.isNull(childVnode)) {
@@ -7804,13 +7879,7 @@ expectAddlSiblings) {
7804
7879
  nextNode = hydrateNode(nextNode, childVnode, renderer);
7805
7880
  }
7806
7881
  else {
7807
- hasMismatch = true;
7808
- if (process.env.NODE_ENV !== 'production') {
7809
- if (!hasWarned) {
7810
- hasWarned = true;
7811
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Client produced more nodes than the server.`, owner);
7812
- }
7813
- }
7882
+ mismatchedChildren = true;
7814
7883
  mount(childVnode, parentNode, renderer, nextNode);
7815
7884
  nextNode = renderer.nextSibling(childVnode.type === 5 /* VNodeType.Fragment */ ? childVnode.trailing : childVnode.elm);
7816
7885
  }
@@ -7827,12 +7896,7 @@ expectAddlSiblings) {
7827
7896
  // rendered more nodes than the client.
7828
7897
  (!useCommentsForBookends || !expectAddlSiblings) &&
7829
7898
  nextNode) {
7830
- hasMismatch = true;
7831
- if (process.env.NODE_ENV !== 'production') {
7832
- if (!hasWarned) {
7833
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Server rendered more nodes than the client.`, owner);
7834
- }
7835
- }
7899
+ mismatchedChildren = true;
7836
7900
  // nextSibling is mostly harmless, and since we don't have
7837
7901
  // a good reference to what element to act upon, we instead
7838
7902
  // rely on the vm's associated renderer for navigating to the
@@ -7844,6 +7908,14 @@ expectAddlSiblings) {
7844
7908
  removeNode(current, parentNode, renderer);
7845
7909
  } while (nextNode);
7846
7910
  }
7911
+ if (mismatchedChildren) {
7912
+ hasMismatch = true;
7913
+ // We can't know exactly which node(s) caused the delta, but we can provide context (parent) and the mismatched sets
7914
+ if (process.env.NODE_ENV !== 'production') {
7915
+ const clientNodes = shared.ArrayMap.call(children, (c) => c?.elm);
7916
+ queueHydrationError('child node', serverNodes, clientNodes);
7917
+ }
7918
+ }
7847
7919
  }
7848
7920
  function handleMismatch(node, vnode, renderer) {
7849
7921
  hasMismatch = true;
@@ -7859,31 +7931,21 @@ function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7859
7931
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7860
7932
  applyRefs(vnode, vnode.owner);
7861
7933
  }
7862
- function hasCorrectNodeType(vnode, node, nodeType, renderer) {
7863
- const { getProperty } = renderer;
7864
- if (getProperty(node, 'nodeType') !== nodeType) {
7865
- if (process.env.NODE_ENV !== 'production') {
7866
- logWarn('Hydration mismatch: incorrect node type received', vnode.owner);
7867
- }
7868
- return false;
7869
- }
7870
- return true;
7871
- }
7872
7934
  function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true) {
7873
7935
  const { getProperty } = renderer;
7874
7936
  if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
7875
7937
  if (process.env.NODE_ENV !== 'production') {
7876
- logWarn(`Hydration mismatch: expecting element with tag "${vnode.sel.toLowerCase()}" but found "${getProperty(elm, 'tagName').toLowerCase()}".`, vnode.owner);
7938
+ queueHydrationError('node', elm);
7877
7939
  }
7878
7940
  return false;
7879
7941
  }
7880
7942
  const { data } = vnode;
7881
- const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7943
+ const hasCompatibleAttrs = validateAttrs(elm, data, renderer, shouldValidateAttr);
7882
7944
  const hasCompatibleClass = shouldValidateAttr('class')
7883
7945
  ? validateClassAttr(vnode, elm, data, renderer)
7884
7946
  : true;
7885
7947
  const hasCompatibleStyle = shouldValidateAttr('style')
7886
- ? validateStyleAttr(vnode, elm, data, renderer)
7948
+ ? validateStyleAttr(elm, data, renderer)
7887
7949
  : true;
7888
7950
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7889
7951
  }
@@ -7900,7 +7962,7 @@ function attributeValuesAreEqual(vnodeValue, value) {
7900
7962
  // In all other cases, the two values are not considered equal
7901
7963
  return false;
7902
7964
  }
7903
- function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7965
+ function validateAttrs(elm, data, renderer, shouldValidateAttr) {
7904
7966
  const { attrs = {} } = data;
7905
7967
  let nodesAreCompatible = true;
7906
7968
  // Validate attributes, though we could always recovery from those by running the update mods.
@@ -7913,8 +7975,7 @@ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7913
7975
  const elmAttrValue = getAttribute(elm, attrName);
7914
7976
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7915
7977
  if (process.env.NODE_ENV !== 'production') {
7916
- const { getProperty } = renderer;
7917
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found ${shared.isNull(elmAttrValue) ? 'null' : `"${elmAttrValue}"`}`, vnode.owner);
7978
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, elmAttrValue), prettyPrintAttribute(attrName, attrValue));
7918
7979
  }
7919
7980
  nodesAreCompatible = false;
7920
7981
  }
@@ -7942,7 +8003,6 @@ function validateClassAttr(vnode, elm, data, renderer) {
7942
8003
  // classMap is never available on VStaticPartData so it can default to undefined
7943
8004
  // casting to prevent TS error.
7944
8005
  const { className, classMap } = data;
7945
- const { getProperty } = renderer;
7946
8006
  // ---------- Step 1: get the classes from the element and the vnode
7947
8007
  // Use a Set because we don't care to validate mismatches for 1) different ordering in SSR vs CSR, or 2)
7948
8008
  // duplicated class names. These don't have an effect on rendered styles.
@@ -7988,12 +8048,11 @@ function validateClassAttr(vnode, elm, data, renderer) {
7988
8048
  // ---------- Step 3: check for compatibility
7989
8049
  const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);
7990
8050
  if (process.env.NODE_ENV !== 'production' && !classesAreCompatible) {
7991
- const prettyPrint = (set) => JSON.stringify(shared.ArrayJoin.call(shared.ArraySort.call(shared.ArrayFrom(set)), ' '));
7992
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "class" has different values, expected ${prettyPrint(vnodeClasses)} but found ${prettyPrint(elmClasses)}`, vnode.owner);
8051
+ queueHydrationError('attribute', prettyPrintClasses(elmClasses), prettyPrintClasses(vnodeClasses));
7993
8052
  }
7994
8053
  return classesAreCompatible;
7995
8054
  }
7996
- function validateStyleAttr(vnode, elm, data, renderer) {
8055
+ function validateStyleAttr(elm, data, renderer) {
7997
8056
  // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7998
8057
  const { style, styleDecls } = data;
7999
8058
  const { getAttribute } = renderer;
@@ -8027,49 +8086,33 @@ function validateStyleAttr(vnode, elm, data, renderer) {
8027
8086
  }
8028
8087
  vnodeStyle = shared.ArrayJoin.call(expectedStyle, ' ');
8029
8088
  }
8030
- if (!nodesAreCompatible) {
8031
- if (process.env.NODE_ENV !== 'production') {
8032
- const { getProperty } = renderer;
8033
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "style" has different values, expected "${vnodeStyle}" but found "${elmStyle}".`, vnode.owner);
8034
- }
8089
+ if (process.env.NODE_ENV !== 'production' && !nodesAreCompatible) {
8090
+ queueHydrationError('attribute', prettyPrintAttribute('style', elmStyle), prettyPrintAttribute('style', vnodeStyle));
8035
8091
  }
8036
8092
  return nodesAreCompatible;
8037
8093
  }
8038
- function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8094
+ function areStaticElementsCompatible(clientElement, serverElement, vnode, renderer) {
8039
8095
  const { getProperty, getAttribute } = renderer;
8040
- if (getProperty(client, 'nodeType') === 3 /* EnvNodeTypes.TEXT */) {
8041
- if (!hasCorrectNodeType(vnode, ssr, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8042
- return false;
8043
- }
8044
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8045
- }
8046
- if (getProperty(client, 'nodeType') === 8 /* EnvNodeTypes.COMMENT */) {
8047
- if (!hasCorrectNodeType(vnode, ssr, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
8048
- return false;
8049
- }
8050
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8051
- }
8052
- if (!hasCorrectNodeType(vnode, ssr, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8053
- return false;
8054
- }
8055
- const { owner, parts } = vnode;
8096
+ const { parts } = vnode;
8056
8097
  let isCompatibleElements = true;
8057
- if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
8098
+ if (getProperty(clientElement, 'tagName') !== getProperty(serverElement, 'tagName')) {
8058
8099
  if (process.env.NODE_ENV !== 'production') {
8059
- logWarn(`Hydration mismatch: expecting element with tag "${getProperty(client, 'tagName').toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, owner);
8100
+ queueHydrationError('node', serverElement);
8060
8101
  }
8061
8102
  return false;
8062
8103
  }
8063
- const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
8104
+ const clientAttrsNames = getProperty(clientElement, 'getAttributeNames').call(clientElement);
8064
8105
  clientAttrsNames.forEach((attrName) => {
8065
- if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
8106
+ const clientAttributeValue = getAttribute(clientElement, attrName);
8107
+ const serverAttributeValue = getAttribute(serverElement, attrName);
8108
+ if (clientAttributeValue !== serverAttributeValue) {
8066
8109
  // Check if the root element attributes have expressions, if it does then we need to delegate hydration
8067
8110
  // validation to haveCompatibleStaticParts.
8068
8111
  // Note if there are no parts then it is a fully static fragment.
8069
8112
  // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
8070
8113
  if (parts?.[0].partId !== 0) {
8071
8114
  if (process.env.NODE_ENV !== 'production') {
8072
- logWarn(`Mismatch hydrating element <${getProperty(client, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${getAttribute(client, attrName)}" but found "${getAttribute(ssr, attrName)}"`, owner);
8115
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, serverAttributeValue), prettyPrintAttribute(attrName, clientAttributeValue));
8073
8116
  }
8074
8117
  isCompatibleElements = false;
8075
8118
  }
@@ -8078,7 +8121,7 @@ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8078
8121
  return isCompatibleElements;
8079
8122
  }
8080
8123
  function haveCompatibleStaticParts(vnode, renderer) {
8081
- const { parts, owner } = vnode;
8124
+ const { parts } = vnode;
8082
8125
  if (shared.isUndefined(parts)) {
8083
8126
  return true;
8084
8127
  }
@@ -8089,11 +8132,11 @@ function haveCompatibleStaticParts(vnode, renderer) {
8089
8132
  for (const part of parts) {
8090
8133
  const { elm } = part;
8091
8134
  if (isVStaticPartElement(part)) {
8092
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8135
+ if (!isTypeElement(elm)) {
8093
8136
  return false;
8094
8137
  }
8095
8138
  const { data } = part;
8096
- const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
8139
+ const hasMatchingAttrs = validateAttrs(elm, data, renderer, () => true);
8097
8140
  // Explicitly skip hydration validation when static parts don't contain `style` or `className`.
8098
8141
  // This means the style/class attributes are either static or don't exist on the element and
8099
8142
  // cannot be affected by hydration.
@@ -8103,7 +8146,7 @@ function haveCompatibleStaticParts(vnode, renderer) {
8103
8146
  ? validateClassAttr(vnode, elm, data, renderer)
8104
8147
  : true;
8105
8148
  const hasMatchingStyleAttr = shouldValidateAttr(data, 'style')
8106
- ? validateStyleAttr(vnode, elm, data, renderer)
8149
+ ? validateStyleAttr(elm, data, renderer)
8107
8150
  : true;
8108
8151
  if (shared.isFalse(hasMatchingAttrs && hasMatchingClass && hasMatchingStyleAttr)) {
8109
8152
  return false;
@@ -8111,10 +8154,10 @@ function haveCompatibleStaticParts(vnode, renderer) {
8111
8154
  }
8112
8155
  else {
8113
8156
  // VStaticPartText
8114
- if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8157
+ if (!isTypeText(elm)) {
8115
8158
  return false;
8116
8159
  }
8117
- updateTextContent(elm, part, owner, renderer);
8160
+ updateTextContent(elm, part, renderer);
8118
8161
  }
8119
8162
  }
8120
8163
  return true;
@@ -8431,5 +8474,5 @@ exports.swapTemplate = swapTemplate;
8431
8474
  exports.track = track;
8432
8475
  exports.unwrap = unwrap;
8433
8476
  exports.wire = wire;
8434
- /** version: 8.12.4 */
8477
+ /** version: 8.12.5 */
8435
8478
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -3974,15 +3974,6 @@ function safelySetProperty(setProperty, elm, key, value) {
3974
3974
  setProperty(elm, key, value);
3975
3975
  }
3976
3976
  }
3977
- /**
3978
- * Given two objects (likely either a string or a SanitizedHtmlContent object), return true if their
3979
- * string values are equivalent.
3980
- * @param first
3981
- * @param second
3982
- */
3983
- function isSanitizedHtmlContentEqual(first, second) {
3984
- return unwrapIfNecessary(first) === unwrapIfNecessary(second);
3985
- }
3986
3977
 
3987
3978
  /*
3988
3979
  * Copyright (c) 2018, salesforce.com, inc.
@@ -7535,6 +7526,78 @@ if (process.env.IS_BROWSER && isGlobalAriaPolyfillLoaded()) {
7535
7526
  }
7536
7527
  }
7537
7528
 
7529
+ /*
7530
+ * Copyright (c) 2024, Salesforce, Inc.
7531
+ * All rights reserved.
7532
+ * SPDX-License-Identifier: MIT
7533
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
7534
+ */
7535
+ // Errors that occured during the hydration process
7536
+ let hydrationErrors = [];
7537
+ /*
7538
+ Prints attributes as null or "value"
7539
+ */
7540
+ function prettyPrintAttribute(attribute, value) {
7541
+ assertNotProd(); // this method should never leak to prod
7542
+ return `${attribute}=${isNull(value) || isUndefined$1(value) ? value : `"${value}"`}`;
7543
+ }
7544
+ /*
7545
+ Sorts and stringifies classes
7546
+ */
7547
+ function prettyPrintClasses(classes) {
7548
+ assertNotProd(); // this method should never leak to prod
7549
+ const value = JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(classes)), ' '));
7550
+ return `class=${value}`;
7551
+ }
7552
+ /*
7553
+ Hydration errors occur before the source node has been fully hydrated,
7554
+ queue them so they can be logged later against the mounted node.
7555
+ */
7556
+ function queueHydrationError(type, serverRendered, clientExpected) {
7557
+ assertNotProd(); // this method should never leak to prod
7558
+ ArrayPush$1.call(hydrationErrors, { type, serverRendered, clientExpected });
7559
+ }
7560
+ /*
7561
+ Flushes (logs) any queued errors after the source node has been mounted.
7562
+ */
7563
+ function flushHydrationErrors(source) {
7564
+ assertNotProd(); // this method should never leak to prod
7565
+ for (const hydrationError of hydrationErrors) {
7566
+ logHydrationWarning(`Hydration ${hydrationError.type} mismatch on:`, source, `\n- rendered on server:`, hydrationError.serverRendered, `\n- expected on client:`, hydrationError.clientExpected || source);
7567
+ }
7568
+ hydrationErrors = [];
7569
+ }
7570
+ function isTypeElement(node) {
7571
+ const isCorrectType = node?.nodeType === 1 /* EnvNodeTypes.ELEMENT */;
7572
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7573
+ queueHydrationError('node', node);
7574
+ }
7575
+ return isCorrectType;
7576
+ }
7577
+ function isTypeText(node) {
7578
+ const isCorrectType = node?.nodeType === 3 /* EnvNodeTypes.TEXT */;
7579
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7580
+ queueHydrationError('node', node);
7581
+ }
7582
+ return isCorrectType;
7583
+ }
7584
+ function isTypeComment(node) {
7585
+ const isCorrectType = node?.nodeType === 8 /* EnvNodeTypes.COMMENT */;
7586
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7587
+ queueHydrationError('node', node);
7588
+ }
7589
+ return isCorrectType;
7590
+ }
7591
+ /*
7592
+ logger.ts converts all args to a string, losing object referenences and has
7593
+ legacy bloat which would have meant more pathing.
7594
+ */
7595
+ function logHydrationWarning(...args) {
7596
+ assertNotProd(); // this method should never leak to prod
7597
+ /* eslint-disable-next-line no-console */
7598
+ console.warn('[LWC warn:', ...args);
7599
+ }
7600
+
7538
7601
  /*
7539
7602
  * Copyright (c) 2022, salesforce.com, inc.
7540
7603
  * All rights reserved.
@@ -7549,8 +7612,15 @@ function hydrateRoot(vm) {
7549
7612
  hasMismatch = false;
7550
7613
  runConnectedCallback(vm);
7551
7614
  hydrateVM(vm);
7552
- if (hasMismatch && process.env.NODE_ENV !== 'production') {
7553
- logWarn('Hydration completed with errors.', vm);
7615
+ if (process.env.NODE_ENV !== 'production') {
7616
+ /*
7617
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7618
+ Means the element in the console matches what is on the page and the highlighting works properly when you hover over the elements in the console.
7619
+ */
7620
+ flushHydrationErrors(vm.renderRoot);
7621
+ if (hasMismatch) {
7622
+ logHydrationWarning('Hydration completed with errors.');
7623
+ }
7554
7624
  }
7555
7625
  }
7556
7626
  function hydrateVM(vm) {
@@ -7588,21 +7658,25 @@ function hydrateNode(node, vnode, renderer) {
7588
7658
  hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer);
7589
7659
  break;
7590
7660
  }
7661
+ if (process.env.NODE_ENV !== 'production') {
7662
+ /*
7663
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7664
+ Means the element in the console matches what is on the page and the highlighting works properly when you hover over the elements in the console.
7665
+ */
7666
+ flushHydrationErrors(hydratedNode);
7667
+ }
7591
7668
  return renderer.nextSibling(hydratedNode);
7592
7669
  }
7593
7670
  const NODE_VALUE_PROP = 'nodeValue';
7594
- function textNodeContentsAreEqual(node, vnode, renderer) {
7671
+ function validateTextNodeEquality(node, vnode, renderer) {
7595
7672
  const { getProperty } = renderer;
7596
7673
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7597
- if (nodeValue === vnode.text) {
7598
- return true;
7599
- }
7600
- // Special case for empty text nodes these are serialized differently on the server
7601
- // See https://github.com/salesforce/lwc/pull/2656
7602
- if (nodeValue === '\u200D' && vnode.text === '') {
7603
- return true;
7674
+ if (nodeValue !== vnode.text &&
7675
+ // Special case for empty text nodes – these are serialized differently on the server
7676
+ // See https://github.com/salesforce/lwc/pull/2656
7677
+ (nodeValue !== '\u200D' || vnode.text !== '')) {
7678
+ queueHydrationError('text content', nodeValue, vnode.text);
7604
7679
  }
7605
- return false;
7606
7680
  }
7607
7681
  // The validationOptOut static property can be an array of attribute names.
7608
7682
  // Any attribute names specified in that array will not be validated, and the
@@ -7625,7 +7699,7 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7625
7699
  !isUndefined$1(optOutStaticProp) &&
7626
7700
  !isTrue(optOutStaticProp) &&
7627
7701
  !isValidArray) {
7628
- logWarn('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7702
+ logHydrationWarning('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7629
7703
  }
7630
7704
  return (attrName) => {
7631
7705
  // Component wants to opt out of all validation
@@ -7645,16 +7719,14 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7645
7719
  };
7646
7720
  }
7647
7721
  function hydrateText(node, vnode, renderer) {
7648
- if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7722
+ if (!isTypeText(node)) {
7649
7723
  return handleMismatch(node, vnode, renderer);
7650
7724
  }
7651
- return updateTextContent(node, vnode, vnode.owner, renderer);
7725
+ return updateTextContent(node, vnode, renderer);
7652
7726
  }
7653
- function updateTextContent(node, vnode, owner, renderer) {
7727
+ function updateTextContent(node, vnode, renderer) {
7654
7728
  if (process.env.NODE_ENV !== 'production') {
7655
- if (!textNodeContentsAreEqual(node, vnode, renderer)) {
7656
- logWarn('Hydration mismatch: text values do not match, will recover from the difference', owner);
7657
- }
7729
+ validateTextNodeEquality(node, vnode, renderer);
7658
7730
  }
7659
7731
  const { setText } = renderer;
7660
7732
  setText(node, vnode.text ?? null);
@@ -7662,14 +7734,14 @@ function updateTextContent(node, vnode, owner, renderer) {
7662
7734
  return node;
7663
7735
  }
7664
7736
  function hydrateComment(node, vnode, renderer) {
7665
- if (!hasCorrectNodeType(vnode, node, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
7737
+ if (!isTypeComment(node)) {
7666
7738
  return handleMismatch(node, vnode, renderer);
7667
7739
  }
7668
7740
  if (process.env.NODE_ENV !== 'production') {
7669
7741
  const { getProperty } = renderer;
7670
7742
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7671
7743
  if (nodeValue !== vnode.text) {
7672
- logWarn('Hydration mismatch: comment values do not match, will recover from the difference', vnode.owner);
7744
+ queueHydrationError('comment', nodeValue, vnode.text);
7673
7745
  }
7674
7746
  }
7675
7747
  const { setProperty } = renderer;
@@ -7680,11 +7752,12 @@ function hydrateComment(node, vnode, renderer) {
7680
7752
  return node;
7681
7753
  }
7682
7754
  function hydrateStaticElement(elm, vnode, renderer) {
7683
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7684
- !areCompatibleStaticNodes(vnode.fragment, elm, vnode, renderer)) {
7685
- return handleMismatch(elm, vnode, renderer);
7755
+ if (isTypeElement(elm) &&
7756
+ isTypeElement(vnode.fragment) &&
7757
+ areStaticElementsCompatible(vnode.fragment, elm, vnode, renderer)) {
7758
+ return hydrateStaticElementParts(elm, vnode, renderer);
7686
7759
  }
7687
- return hydrateStaticElementParts(elm, vnode, renderer);
7760
+ return handleMismatch(elm, vnode, renderer);
7688
7761
  }
7689
7762
  function hydrateStaticElementParts(elm, vnode, renderer) {
7690
7763
  const { parts } = vnode;
@@ -7707,8 +7780,7 @@ function hydrateFragment(elm, vnode, renderer) {
7707
7780
  return (vnode.elm = children[children.length - 1].elm);
7708
7781
  }
7709
7782
  function hydrateElement(elm, vnode, renderer) {
7710
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7711
- !isMatchingElement(vnode, elm, renderer)) {
7783
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer)) {
7712
7784
  return handleMismatch(elm, vnode, renderer);
7713
7785
  }
7714
7786
  vnode.elm = elm;
@@ -7721,17 +7793,17 @@ function hydrateElement(elm, vnode, renderer) {
7721
7793
  const { data: { props }, } = vnode;
7722
7794
  const { getProperty } = renderer;
7723
7795
  if (!isUndefined$1(props) && !isUndefined$1(props.innerHTML)) {
7724
- if (isSanitizedHtmlContentEqual(getProperty(elm, 'innerHTML'), props.innerHTML)) {
7796
+ const unwrappedServerInnerHTML = unwrapIfNecessary(getProperty(elm, 'innerHTML'));
7797
+ const unwrappedClientInnerHTML = unwrapIfNecessary(props.innerHTML);
7798
+ if (unwrappedServerInnerHTML === unwrappedClientInnerHTML) {
7725
7799
  // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization
7726
7800
  vnode.data = {
7727
7801
  ...vnode.data,
7728
7802
  props: cloneAndOmitKey(props, 'innerHTML'),
7729
7803
  };
7730
7804
  }
7731
- else {
7732
- if (process.env.NODE_ENV !== 'production') {
7733
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: innerHTML values do not match for element, will recover from the difference`, owner);
7734
- }
7805
+ else if (process.env.NODE_ENV !== 'production') {
7806
+ queueHydrationError('innerHTML', unwrappedServerInnerHTML, unwrappedClientInnerHTML);
7735
7807
  }
7736
7808
  }
7737
7809
  }
@@ -7754,8 +7826,7 @@ function hydrateCustomElement(elm, vnode, renderer) {
7754
7826
  //
7755
7827
  // Therefore, if validationOptOut is falsey or an array of strings, we need to
7756
7828
  // examine some or all of the custom element's attributes.
7757
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7758
- !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7829
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7759
7830
  return handleMismatch(elm, vnode, renderer);
7760
7831
  }
7761
7832
  const { sel, mode, ctor, owner } = vnode;
@@ -7791,9 +7862,13 @@ function hydrateChildren(node, children, parentNode, owner,
7791
7862
  // last node of the fragment. Hydration should not fail if a trailing sibling is
7792
7863
  // found in this case.
7793
7864
  expectAddlSiblings) {
7794
- let hasWarned = false;
7865
+ let mismatchedChildren = false;
7795
7866
  let nextNode = node;
7796
7867
  const { renderer } = owner;
7868
+ const { getChildNodes, cloneNode } = renderer;
7869
+ const serverNodes = process.env.NODE_ENV !== 'production'
7870
+ ? Array.from(getChildNodes(parentNode), (node) => cloneNode(node, true))
7871
+ : null;
7797
7872
  for (let i = 0; i < children.length; i++) {
7798
7873
  const childVnode = children[i];
7799
7874
  if (!isNull(childVnode)) {
@@ -7801,13 +7876,7 @@ expectAddlSiblings) {
7801
7876
  nextNode = hydrateNode(nextNode, childVnode, renderer);
7802
7877
  }
7803
7878
  else {
7804
- hasMismatch = true;
7805
- if (process.env.NODE_ENV !== 'production') {
7806
- if (!hasWarned) {
7807
- hasWarned = true;
7808
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Client produced more nodes than the server.`, owner);
7809
- }
7810
- }
7879
+ mismatchedChildren = true;
7811
7880
  mount(childVnode, parentNode, renderer, nextNode);
7812
7881
  nextNode = renderer.nextSibling(childVnode.type === 5 /* VNodeType.Fragment */ ? childVnode.trailing : childVnode.elm);
7813
7882
  }
@@ -7824,12 +7893,7 @@ expectAddlSiblings) {
7824
7893
  // rendered more nodes than the client.
7825
7894
  (!useCommentsForBookends || !expectAddlSiblings) &&
7826
7895
  nextNode) {
7827
- hasMismatch = true;
7828
- if (process.env.NODE_ENV !== 'production') {
7829
- if (!hasWarned) {
7830
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Server rendered more nodes than the client.`, owner);
7831
- }
7832
- }
7896
+ mismatchedChildren = true;
7833
7897
  // nextSibling is mostly harmless, and since we don't have
7834
7898
  // a good reference to what element to act upon, we instead
7835
7899
  // rely on the vm's associated renderer for navigating to the
@@ -7841,6 +7905,14 @@ expectAddlSiblings) {
7841
7905
  removeNode(current, parentNode, renderer);
7842
7906
  } while (nextNode);
7843
7907
  }
7908
+ if (mismatchedChildren) {
7909
+ hasMismatch = true;
7910
+ // We can't know exactly which node(s) caused the delta, but we can provide context (parent) and the mismatched sets
7911
+ if (process.env.NODE_ENV !== 'production') {
7912
+ const clientNodes = ArrayMap.call(children, (c) => c?.elm);
7913
+ queueHydrationError('child node', serverNodes, clientNodes);
7914
+ }
7915
+ }
7844
7916
  }
7845
7917
  function handleMismatch(node, vnode, renderer) {
7846
7918
  hasMismatch = true;
@@ -7856,31 +7928,21 @@ function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7856
7928
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7857
7929
  applyRefs(vnode, vnode.owner);
7858
7930
  }
7859
- function hasCorrectNodeType(vnode, node, nodeType, renderer) {
7860
- const { getProperty } = renderer;
7861
- if (getProperty(node, 'nodeType') !== nodeType) {
7862
- if (process.env.NODE_ENV !== 'production') {
7863
- logWarn('Hydration mismatch: incorrect node type received', vnode.owner);
7864
- }
7865
- return false;
7866
- }
7867
- return true;
7868
- }
7869
7931
  function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true) {
7870
7932
  const { getProperty } = renderer;
7871
7933
  if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
7872
7934
  if (process.env.NODE_ENV !== 'production') {
7873
- logWarn(`Hydration mismatch: expecting element with tag "${vnode.sel.toLowerCase()}" but found "${getProperty(elm, 'tagName').toLowerCase()}".`, vnode.owner);
7935
+ queueHydrationError('node', elm);
7874
7936
  }
7875
7937
  return false;
7876
7938
  }
7877
7939
  const { data } = vnode;
7878
- const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7940
+ const hasCompatibleAttrs = validateAttrs(elm, data, renderer, shouldValidateAttr);
7879
7941
  const hasCompatibleClass = shouldValidateAttr('class')
7880
7942
  ? validateClassAttr(vnode, elm, data, renderer)
7881
7943
  : true;
7882
7944
  const hasCompatibleStyle = shouldValidateAttr('style')
7883
- ? validateStyleAttr(vnode, elm, data, renderer)
7945
+ ? validateStyleAttr(elm, data, renderer)
7884
7946
  : true;
7885
7947
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7886
7948
  }
@@ -7897,7 +7959,7 @@ function attributeValuesAreEqual(vnodeValue, value) {
7897
7959
  // In all other cases, the two values are not considered equal
7898
7960
  return false;
7899
7961
  }
7900
- function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7962
+ function validateAttrs(elm, data, renderer, shouldValidateAttr) {
7901
7963
  const { attrs = {} } = data;
7902
7964
  let nodesAreCompatible = true;
7903
7965
  // Validate attributes, though we could always recovery from those by running the update mods.
@@ -7910,8 +7972,7 @@ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7910
7972
  const elmAttrValue = getAttribute(elm, attrName);
7911
7973
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7912
7974
  if (process.env.NODE_ENV !== 'production') {
7913
- const { getProperty } = renderer;
7914
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found ${isNull(elmAttrValue) ? 'null' : `"${elmAttrValue}"`}`, vnode.owner);
7975
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, elmAttrValue), prettyPrintAttribute(attrName, attrValue));
7915
7976
  }
7916
7977
  nodesAreCompatible = false;
7917
7978
  }
@@ -7939,7 +8000,6 @@ function validateClassAttr(vnode, elm, data, renderer) {
7939
8000
  // classMap is never available on VStaticPartData so it can default to undefined
7940
8001
  // casting to prevent TS error.
7941
8002
  const { className, classMap } = data;
7942
- const { getProperty } = renderer;
7943
8003
  // ---------- Step 1: get the classes from the element and the vnode
7944
8004
  // Use a Set because we don't care to validate mismatches for 1) different ordering in SSR vs CSR, or 2)
7945
8005
  // duplicated class names. These don't have an effect on rendered styles.
@@ -7985,12 +8045,11 @@ function validateClassAttr(vnode, elm, data, renderer) {
7985
8045
  // ---------- Step 3: check for compatibility
7986
8046
  const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);
7987
8047
  if (process.env.NODE_ENV !== 'production' && !classesAreCompatible) {
7988
- const prettyPrint = (set) => JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(set)), ' '));
7989
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "class" has different values, expected ${prettyPrint(vnodeClasses)} but found ${prettyPrint(elmClasses)}`, vnode.owner);
8048
+ queueHydrationError('attribute', prettyPrintClasses(elmClasses), prettyPrintClasses(vnodeClasses));
7990
8049
  }
7991
8050
  return classesAreCompatible;
7992
8051
  }
7993
- function validateStyleAttr(vnode, elm, data, renderer) {
8052
+ function validateStyleAttr(elm, data, renderer) {
7994
8053
  // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7995
8054
  const { style, styleDecls } = data;
7996
8055
  const { getAttribute } = renderer;
@@ -8024,49 +8083,33 @@ function validateStyleAttr(vnode, elm, data, renderer) {
8024
8083
  }
8025
8084
  vnodeStyle = ArrayJoin.call(expectedStyle, ' ');
8026
8085
  }
8027
- if (!nodesAreCompatible) {
8028
- if (process.env.NODE_ENV !== 'production') {
8029
- const { getProperty } = renderer;
8030
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "style" has different values, expected "${vnodeStyle}" but found "${elmStyle}".`, vnode.owner);
8031
- }
8086
+ if (process.env.NODE_ENV !== 'production' && !nodesAreCompatible) {
8087
+ queueHydrationError('attribute', prettyPrintAttribute('style', elmStyle), prettyPrintAttribute('style', vnodeStyle));
8032
8088
  }
8033
8089
  return nodesAreCompatible;
8034
8090
  }
8035
- function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8091
+ function areStaticElementsCompatible(clientElement, serverElement, vnode, renderer) {
8036
8092
  const { getProperty, getAttribute } = renderer;
8037
- if (getProperty(client, 'nodeType') === 3 /* EnvNodeTypes.TEXT */) {
8038
- if (!hasCorrectNodeType(vnode, ssr, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8039
- return false;
8040
- }
8041
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8042
- }
8043
- if (getProperty(client, 'nodeType') === 8 /* EnvNodeTypes.COMMENT */) {
8044
- if (!hasCorrectNodeType(vnode, ssr, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
8045
- return false;
8046
- }
8047
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8048
- }
8049
- if (!hasCorrectNodeType(vnode, ssr, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8050
- return false;
8051
- }
8052
- const { owner, parts } = vnode;
8093
+ const { parts } = vnode;
8053
8094
  let isCompatibleElements = true;
8054
- if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
8095
+ if (getProperty(clientElement, 'tagName') !== getProperty(serverElement, 'tagName')) {
8055
8096
  if (process.env.NODE_ENV !== 'production') {
8056
- logWarn(`Hydration mismatch: expecting element with tag "${getProperty(client, 'tagName').toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, owner);
8097
+ queueHydrationError('node', serverElement);
8057
8098
  }
8058
8099
  return false;
8059
8100
  }
8060
- const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
8101
+ const clientAttrsNames = getProperty(clientElement, 'getAttributeNames').call(clientElement);
8061
8102
  clientAttrsNames.forEach((attrName) => {
8062
- if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
8103
+ const clientAttributeValue = getAttribute(clientElement, attrName);
8104
+ const serverAttributeValue = getAttribute(serverElement, attrName);
8105
+ if (clientAttributeValue !== serverAttributeValue) {
8063
8106
  // Check if the root element attributes have expressions, if it does then we need to delegate hydration
8064
8107
  // validation to haveCompatibleStaticParts.
8065
8108
  // Note if there are no parts then it is a fully static fragment.
8066
8109
  // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
8067
8110
  if (parts?.[0].partId !== 0) {
8068
8111
  if (process.env.NODE_ENV !== 'production') {
8069
- logWarn(`Mismatch hydrating element <${getProperty(client, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${getAttribute(client, attrName)}" but found "${getAttribute(ssr, attrName)}"`, owner);
8112
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, serverAttributeValue), prettyPrintAttribute(attrName, clientAttributeValue));
8070
8113
  }
8071
8114
  isCompatibleElements = false;
8072
8115
  }
@@ -8075,7 +8118,7 @@ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8075
8118
  return isCompatibleElements;
8076
8119
  }
8077
8120
  function haveCompatibleStaticParts(vnode, renderer) {
8078
- const { parts, owner } = vnode;
8121
+ const { parts } = vnode;
8079
8122
  if (isUndefined$1(parts)) {
8080
8123
  return true;
8081
8124
  }
@@ -8086,11 +8129,11 @@ function haveCompatibleStaticParts(vnode, renderer) {
8086
8129
  for (const part of parts) {
8087
8130
  const { elm } = part;
8088
8131
  if (isVStaticPartElement(part)) {
8089
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8132
+ if (!isTypeElement(elm)) {
8090
8133
  return false;
8091
8134
  }
8092
8135
  const { data } = part;
8093
- const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
8136
+ const hasMatchingAttrs = validateAttrs(elm, data, renderer, () => true);
8094
8137
  // Explicitly skip hydration validation when static parts don't contain `style` or `className`.
8095
8138
  // This means the style/class attributes are either static or don't exist on the element and
8096
8139
  // cannot be affected by hydration.
@@ -8100,7 +8143,7 @@ function haveCompatibleStaticParts(vnode, renderer) {
8100
8143
  ? validateClassAttr(vnode, elm, data, renderer)
8101
8144
  : true;
8102
8145
  const hasMatchingStyleAttr = shouldValidateAttr(data, 'style')
8103
- ? validateStyleAttr(vnode, elm, data, renderer)
8146
+ ? validateStyleAttr(elm, data, renderer)
8104
8147
  : true;
8105
8148
  if (isFalse(hasMatchingAttrs && hasMatchingClass && hasMatchingStyleAttr)) {
8106
8149
  return false;
@@ -8108,10 +8151,10 @@ function haveCompatibleStaticParts(vnode, renderer) {
8108
8151
  }
8109
8152
  else {
8110
8153
  // VStaticPartText
8111
- if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8154
+ if (!isTypeText(elm)) {
8112
8155
  return false;
8113
8156
  }
8114
- updateTextContent(elm, part, owner, renderer);
8157
+ updateTextContent(elm, part, renderer);
8115
8158
  }
8116
8159
  }
8117
8160
  return true;
@@ -8377,5 +8420,5 @@ function readonly(obj) {
8377
8420
  }
8378
8421
 
8379
8422
  export { BaseBridgeElement, LightningElement, profilerControl as __unstable__ProfilerControl, reportingControl as __unstable__ReportingControl, api$1 as api, computeShadowAndRenderMode, connectRootElement, createContextProviderWithRegister, createVM, disconnectRootElement, freezeTemplate, getAssociatedVMIfPresent, getComponentAPIVersion, getComponentConstructor, getComponentDef, getComponentHtmlPrototype, hydrateRoot, isComponentConstructor, parseFragment, parseSVGFragment, readonly, registerComponent, registerDecorators, registerTemplate, runFormAssociatedCallback, runFormDisabledCallback, runFormResetCallback, runFormStateRestoreCallback, sanitizeAttribute, shouldBeFormAssociated, swapComponent, swapStyle, swapTemplate, track, unwrap, wire };
8380
- /** version: 8.12.4 */
8423
+ /** version: 8.12.5 */
8381
8424
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/engine-core",
7
- "version": "8.12.4",
7
+ "version": "8.12.5",
8
8
  "description": "Core LWC engine APIs.",
9
9
  "keywords": [
10
10
  "lwc"
@@ -46,9 +46,9 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@lwc/features": "8.12.4",
50
- "@lwc/shared": "8.12.4",
51
- "@lwc/signals": "8.12.4"
49
+ "@lwc/features": "8.12.5",
50
+ "@lwc/shared": "8.12.5",
51
+ "@lwc/signals": "8.12.5"
52
52
  },
53
53
  "devDependencies": {
54
54
  "observable-membrane": "2.0.0"