@lwc/engine-core 8.12.3 → 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.
package/LICENSE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MIT LICENSE
4
4
 
5
- Copyright (c) 2024, Salesforce, Inc.
5
+ Copyright (c) 2025, Salesforce, Inc.
6
6
  All rights reserved.
7
7
 
8
8
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@@ -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
@@ -12,5 +12,6 @@ export declare function cloneAndOmitKey(object: {
12
12
  };
13
13
  export declare function assertNotProd(): void;
14
14
  export declare function shouldBeFormAssociated(Ctor: LightningElementConstructor): boolean;
15
+ export declare function safeHasProp<K extends PropertyKey>(obj: unknown, prop: K): obj is Record<K, unknown>;
15
16
  export {};
16
17
  //# sourceMappingURL=utils.d.ts.map
package/dist/index.cjs.js CHANGED
@@ -251,6 +251,16 @@ function shouldBeFormAssociated(Ctor) {
251
251
  }
252
252
  return ctorFormAssociated && apiFeatureEnabled;
253
253
  }
254
+ // check if a property is in an object, and if the object throws an error merely because we are
255
+ // checking if the property exists, return false
256
+ function safeHasProp(obj, prop) {
257
+ try {
258
+ return prop in obj;
259
+ }
260
+ catch (_err) {
261
+ return false;
262
+ }
263
+ }
254
264
 
255
265
  /*
256
266
  * Copyright (c) 2024, Salesforce, Inc.
@@ -591,14 +601,14 @@ function componentValueObserved(vm, key, target = {}) {
591
601
  valueObserved(component, key);
592
602
  }
593
603
  // The portion of reactivity that's exposed to signals is to subscribe a callback to re-render the VM (templates).
594
- // We check check the following to ensure re-render is subscribed at the correct time.
604
+ // We check the following to ensure re-render is subscribed at the correct time.
595
605
  // 1. The template is currently being rendered (there is a template reactive observer)
596
606
  // 2. There was a call to a getter to access the signal (happens during vnode generation)
597
607
  if (lwcRuntimeFlags.ENABLE_EXPERIMENTAL_SIGNALS &&
598
608
  shared.isObject(target) &&
599
609
  !shared.isNull(target) &&
600
- 'value' in target &&
601
- 'subscribe' in target &&
610
+ safeHasProp(target, 'value') &&
611
+ safeHasProp(target, 'subscribe') &&
602
612
  shared.isFunction(target.subscribe) &&
603
613
  shared.isTrustedSignal(target) &&
604
614
  // Only subscribe if a template is being rendered by the engine
@@ -3967,15 +3977,6 @@ function safelySetProperty(setProperty, elm, key, value) {
3967
3977
  setProperty(elm, key, value);
3968
3978
  }
3969
3979
  }
3970
- /**
3971
- * Given two objects (likely either a string or a SanitizedHtmlContent object), return true if their
3972
- * string values are equivalent.
3973
- * @param first
3974
- * @param second
3975
- */
3976
- function isSanitizedHtmlContentEqual(first, second) {
3977
- return unwrapIfNecessary(first) === unwrapIfNecessary(second);
3978
- }
3979
3980
 
3980
3981
  /*
3981
3982
  * Copyright (c) 2018, salesforce.com, inc.
@@ -7528,6 +7529,78 @@ if (process.env.IS_BROWSER && isGlobalAriaPolyfillLoaded()) {
7528
7529
  }
7529
7530
  }
7530
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
+
7531
7604
  /*
7532
7605
  * Copyright (c) 2022, salesforce.com, inc.
7533
7606
  * All rights reserved.
@@ -7542,8 +7615,15 @@ function hydrateRoot(vm) {
7542
7615
  hasMismatch = false;
7543
7616
  runConnectedCallback(vm);
7544
7617
  hydrateVM(vm);
7545
- if (hasMismatch && process.env.NODE_ENV !== 'production') {
7546
- 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
+ }
7547
7627
  }
7548
7628
  }
7549
7629
  function hydrateVM(vm) {
@@ -7581,21 +7661,25 @@ function hydrateNode(node, vnode, renderer) {
7581
7661
  hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer);
7582
7662
  break;
7583
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
+ }
7584
7671
  return renderer.nextSibling(hydratedNode);
7585
7672
  }
7586
7673
  const NODE_VALUE_PROP = 'nodeValue';
7587
- function textNodeContentsAreEqual(node, vnode, renderer) {
7674
+ function validateTextNodeEquality(node, vnode, renderer) {
7588
7675
  const { getProperty } = renderer;
7589
7676
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7590
- if (nodeValue === vnode.text) {
7591
- return true;
7592
- }
7593
- // Special case for empty text nodes these are serialized differently on the server
7594
- // See https://github.com/salesforce/lwc/pull/2656
7595
- if (nodeValue === '\u200D' && vnode.text === '') {
7596
- 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);
7597
7682
  }
7598
- return false;
7599
7683
  }
7600
7684
  // The validationOptOut static property can be an array of attribute names.
7601
7685
  // Any attribute names specified in that array will not be validated, and the
@@ -7618,7 +7702,7 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7618
7702
  !shared.isUndefined(optOutStaticProp) &&
7619
7703
  !shared.isTrue(optOutStaticProp) &&
7620
7704
  !isValidArray) {
7621
- 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.');
7622
7706
  }
7623
7707
  return (attrName) => {
7624
7708
  // Component wants to opt out of all validation
@@ -7638,16 +7722,14 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7638
7722
  };
7639
7723
  }
7640
7724
  function hydrateText(node, vnode, renderer) {
7641
- if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7725
+ if (!isTypeText(node)) {
7642
7726
  return handleMismatch(node, vnode, renderer);
7643
7727
  }
7644
- return updateTextContent(node, vnode, vnode.owner, renderer);
7728
+ return updateTextContent(node, vnode, renderer);
7645
7729
  }
7646
- function updateTextContent(node, vnode, owner, renderer) {
7730
+ function updateTextContent(node, vnode, renderer) {
7647
7731
  if (process.env.NODE_ENV !== 'production') {
7648
- if (!textNodeContentsAreEqual(node, vnode, renderer)) {
7649
- logWarn('Hydration mismatch: text values do not match, will recover from the difference', owner);
7650
- }
7732
+ validateTextNodeEquality(node, vnode, renderer);
7651
7733
  }
7652
7734
  const { setText } = renderer;
7653
7735
  setText(node, vnode.text ?? null);
@@ -7655,14 +7737,14 @@ function updateTextContent(node, vnode, owner, renderer) {
7655
7737
  return node;
7656
7738
  }
7657
7739
  function hydrateComment(node, vnode, renderer) {
7658
- if (!hasCorrectNodeType(vnode, node, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
7740
+ if (!isTypeComment(node)) {
7659
7741
  return handleMismatch(node, vnode, renderer);
7660
7742
  }
7661
7743
  if (process.env.NODE_ENV !== 'production') {
7662
7744
  const { getProperty } = renderer;
7663
7745
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7664
7746
  if (nodeValue !== vnode.text) {
7665
- logWarn('Hydration mismatch: comment values do not match, will recover from the difference', vnode.owner);
7747
+ queueHydrationError('comment', nodeValue, vnode.text);
7666
7748
  }
7667
7749
  }
7668
7750
  const { setProperty } = renderer;
@@ -7673,11 +7755,12 @@ function hydrateComment(node, vnode, renderer) {
7673
7755
  return node;
7674
7756
  }
7675
7757
  function hydrateStaticElement(elm, vnode, renderer) {
7676
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7677
- !areCompatibleStaticNodes(vnode.fragment, elm, vnode, renderer)) {
7678
- 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);
7679
7762
  }
7680
- return hydrateStaticElementParts(elm, vnode, renderer);
7763
+ return handleMismatch(elm, vnode, renderer);
7681
7764
  }
7682
7765
  function hydrateStaticElementParts(elm, vnode, renderer) {
7683
7766
  const { parts } = vnode;
@@ -7700,8 +7783,7 @@ function hydrateFragment(elm, vnode, renderer) {
7700
7783
  return (vnode.elm = children[children.length - 1].elm);
7701
7784
  }
7702
7785
  function hydrateElement(elm, vnode, renderer) {
7703
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7704
- !isMatchingElement(vnode, elm, renderer)) {
7786
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer)) {
7705
7787
  return handleMismatch(elm, vnode, renderer);
7706
7788
  }
7707
7789
  vnode.elm = elm;
@@ -7714,17 +7796,17 @@ function hydrateElement(elm, vnode, renderer) {
7714
7796
  const { data: { props }, } = vnode;
7715
7797
  const { getProperty } = renderer;
7716
7798
  if (!shared.isUndefined(props) && !shared.isUndefined(props.innerHTML)) {
7717
- if (isSanitizedHtmlContentEqual(getProperty(elm, 'innerHTML'), props.innerHTML)) {
7799
+ const unwrappedServerInnerHTML = unwrapIfNecessary(getProperty(elm, 'innerHTML'));
7800
+ const unwrappedClientInnerHTML = unwrapIfNecessary(props.innerHTML);
7801
+ if (unwrappedServerInnerHTML === unwrappedClientInnerHTML) {
7718
7802
  // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization
7719
7803
  vnode.data = {
7720
7804
  ...vnode.data,
7721
7805
  props: cloneAndOmitKey(props, 'innerHTML'),
7722
7806
  };
7723
7807
  }
7724
- else {
7725
- if (process.env.NODE_ENV !== 'production') {
7726
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: innerHTML values do not match for element, will recover from the difference`, owner);
7727
- }
7808
+ else if (process.env.NODE_ENV !== 'production') {
7809
+ queueHydrationError('innerHTML', unwrappedServerInnerHTML, unwrappedClientInnerHTML);
7728
7810
  }
7729
7811
  }
7730
7812
  }
@@ -7747,8 +7829,7 @@ function hydrateCustomElement(elm, vnode, renderer) {
7747
7829
  //
7748
7830
  // Therefore, if validationOptOut is falsey or an array of strings, we need to
7749
7831
  // examine some or all of the custom element's attributes.
7750
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7751
- !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7832
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7752
7833
  return handleMismatch(elm, vnode, renderer);
7753
7834
  }
7754
7835
  const { sel, mode, ctor, owner } = vnode;
@@ -7784,9 +7865,13 @@ function hydrateChildren(node, children, parentNode, owner,
7784
7865
  // last node of the fragment. Hydration should not fail if a trailing sibling is
7785
7866
  // found in this case.
7786
7867
  expectAddlSiblings) {
7787
- let hasWarned = false;
7868
+ let mismatchedChildren = false;
7788
7869
  let nextNode = node;
7789
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;
7790
7875
  for (let i = 0; i < children.length; i++) {
7791
7876
  const childVnode = children[i];
7792
7877
  if (!shared.isNull(childVnode)) {
@@ -7794,13 +7879,7 @@ expectAddlSiblings) {
7794
7879
  nextNode = hydrateNode(nextNode, childVnode, renderer);
7795
7880
  }
7796
7881
  else {
7797
- hasMismatch = true;
7798
- if (process.env.NODE_ENV !== 'production') {
7799
- if (!hasWarned) {
7800
- hasWarned = true;
7801
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Client produced more nodes than the server.`, owner);
7802
- }
7803
- }
7882
+ mismatchedChildren = true;
7804
7883
  mount(childVnode, parentNode, renderer, nextNode);
7805
7884
  nextNode = renderer.nextSibling(childVnode.type === 5 /* VNodeType.Fragment */ ? childVnode.trailing : childVnode.elm);
7806
7885
  }
@@ -7817,12 +7896,7 @@ expectAddlSiblings) {
7817
7896
  // rendered more nodes than the client.
7818
7897
  (!useCommentsForBookends || !expectAddlSiblings) &&
7819
7898
  nextNode) {
7820
- hasMismatch = true;
7821
- if (process.env.NODE_ENV !== 'production') {
7822
- if (!hasWarned) {
7823
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Server rendered more nodes than the client.`, owner);
7824
- }
7825
- }
7899
+ mismatchedChildren = true;
7826
7900
  // nextSibling is mostly harmless, and since we don't have
7827
7901
  // a good reference to what element to act upon, we instead
7828
7902
  // rely on the vm's associated renderer for navigating to the
@@ -7834,6 +7908,14 @@ expectAddlSiblings) {
7834
7908
  removeNode(current, parentNode, renderer);
7835
7909
  } while (nextNode);
7836
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
+ }
7837
7919
  }
7838
7920
  function handleMismatch(node, vnode, renderer) {
7839
7921
  hasMismatch = true;
@@ -7849,31 +7931,21 @@ function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7849
7931
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7850
7932
  applyRefs(vnode, vnode.owner);
7851
7933
  }
7852
- function hasCorrectNodeType(vnode, node, nodeType, renderer) {
7853
- const { getProperty } = renderer;
7854
- if (getProperty(node, 'nodeType') !== nodeType) {
7855
- if (process.env.NODE_ENV !== 'production') {
7856
- logWarn('Hydration mismatch: incorrect node type received', vnode.owner);
7857
- }
7858
- return false;
7859
- }
7860
- return true;
7861
- }
7862
7934
  function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true) {
7863
7935
  const { getProperty } = renderer;
7864
7936
  if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
7865
7937
  if (process.env.NODE_ENV !== 'production') {
7866
- logWarn(`Hydration mismatch: expecting element with tag "${vnode.sel.toLowerCase()}" but found "${getProperty(elm, 'tagName').toLowerCase()}".`, vnode.owner);
7938
+ queueHydrationError('node', elm);
7867
7939
  }
7868
7940
  return false;
7869
7941
  }
7870
7942
  const { data } = vnode;
7871
- const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7943
+ const hasCompatibleAttrs = validateAttrs(elm, data, renderer, shouldValidateAttr);
7872
7944
  const hasCompatibleClass = shouldValidateAttr('class')
7873
7945
  ? validateClassAttr(vnode, elm, data, renderer)
7874
7946
  : true;
7875
7947
  const hasCompatibleStyle = shouldValidateAttr('style')
7876
- ? validateStyleAttr(vnode, elm, data, renderer)
7948
+ ? validateStyleAttr(elm, data, renderer)
7877
7949
  : true;
7878
7950
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7879
7951
  }
@@ -7890,7 +7962,7 @@ function attributeValuesAreEqual(vnodeValue, value) {
7890
7962
  // In all other cases, the two values are not considered equal
7891
7963
  return false;
7892
7964
  }
7893
- function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7965
+ function validateAttrs(elm, data, renderer, shouldValidateAttr) {
7894
7966
  const { attrs = {} } = data;
7895
7967
  let nodesAreCompatible = true;
7896
7968
  // Validate attributes, though we could always recovery from those by running the update mods.
@@ -7903,8 +7975,7 @@ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7903
7975
  const elmAttrValue = getAttribute(elm, attrName);
7904
7976
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7905
7977
  if (process.env.NODE_ENV !== 'production') {
7906
- const { getProperty } = renderer;
7907
- 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));
7908
7979
  }
7909
7980
  nodesAreCompatible = false;
7910
7981
  }
@@ -7932,7 +8003,6 @@ function validateClassAttr(vnode, elm, data, renderer) {
7932
8003
  // classMap is never available on VStaticPartData so it can default to undefined
7933
8004
  // casting to prevent TS error.
7934
8005
  const { className, classMap } = data;
7935
- const { getProperty } = renderer;
7936
8006
  // ---------- Step 1: get the classes from the element and the vnode
7937
8007
  // Use a Set because we don't care to validate mismatches for 1) different ordering in SSR vs CSR, or 2)
7938
8008
  // duplicated class names. These don't have an effect on rendered styles.
@@ -7978,12 +8048,11 @@ function validateClassAttr(vnode, elm, data, renderer) {
7978
8048
  // ---------- Step 3: check for compatibility
7979
8049
  const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);
7980
8050
  if (process.env.NODE_ENV !== 'production' && !classesAreCompatible) {
7981
- const prettyPrint = (set) => JSON.stringify(shared.ArrayJoin.call(shared.ArraySort.call(shared.ArrayFrom(set)), ' '));
7982
- 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));
7983
8052
  }
7984
8053
  return classesAreCompatible;
7985
8054
  }
7986
- function validateStyleAttr(vnode, elm, data, renderer) {
8055
+ function validateStyleAttr(elm, data, renderer) {
7987
8056
  // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7988
8057
  const { style, styleDecls } = data;
7989
8058
  const { getAttribute } = renderer;
@@ -8017,49 +8086,33 @@ function validateStyleAttr(vnode, elm, data, renderer) {
8017
8086
  }
8018
8087
  vnodeStyle = shared.ArrayJoin.call(expectedStyle, ' ');
8019
8088
  }
8020
- if (!nodesAreCompatible) {
8021
- if (process.env.NODE_ENV !== 'production') {
8022
- const { getProperty } = renderer;
8023
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "style" has different values, expected "${vnodeStyle}" but found "${elmStyle}".`, vnode.owner);
8024
- }
8089
+ if (process.env.NODE_ENV !== 'production' && !nodesAreCompatible) {
8090
+ queueHydrationError('attribute', prettyPrintAttribute('style', elmStyle), prettyPrintAttribute('style', vnodeStyle));
8025
8091
  }
8026
8092
  return nodesAreCompatible;
8027
8093
  }
8028
- function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8094
+ function areStaticElementsCompatible(clientElement, serverElement, vnode, renderer) {
8029
8095
  const { getProperty, getAttribute } = renderer;
8030
- if (getProperty(client, 'nodeType') === 3 /* EnvNodeTypes.TEXT */) {
8031
- if (!hasCorrectNodeType(vnode, ssr, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8032
- return false;
8033
- }
8034
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8035
- }
8036
- if (getProperty(client, 'nodeType') === 8 /* EnvNodeTypes.COMMENT */) {
8037
- if (!hasCorrectNodeType(vnode, ssr, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
8038
- return false;
8039
- }
8040
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8041
- }
8042
- if (!hasCorrectNodeType(vnode, ssr, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8043
- return false;
8044
- }
8045
- const { owner, parts } = vnode;
8096
+ const { parts } = vnode;
8046
8097
  let isCompatibleElements = true;
8047
- if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
8098
+ if (getProperty(clientElement, 'tagName') !== getProperty(serverElement, 'tagName')) {
8048
8099
  if (process.env.NODE_ENV !== 'production') {
8049
- logWarn(`Hydration mismatch: expecting element with tag "${getProperty(client, 'tagName').toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, owner);
8100
+ queueHydrationError('node', serverElement);
8050
8101
  }
8051
8102
  return false;
8052
8103
  }
8053
- const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
8104
+ const clientAttrsNames = getProperty(clientElement, 'getAttributeNames').call(clientElement);
8054
8105
  clientAttrsNames.forEach((attrName) => {
8055
- if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
8106
+ const clientAttributeValue = getAttribute(clientElement, attrName);
8107
+ const serverAttributeValue = getAttribute(serverElement, attrName);
8108
+ if (clientAttributeValue !== serverAttributeValue) {
8056
8109
  // Check if the root element attributes have expressions, if it does then we need to delegate hydration
8057
8110
  // validation to haveCompatibleStaticParts.
8058
8111
  // Note if there are no parts then it is a fully static fragment.
8059
8112
  // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
8060
8113
  if (parts?.[0].partId !== 0) {
8061
8114
  if (process.env.NODE_ENV !== 'production') {
8062
- 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));
8063
8116
  }
8064
8117
  isCompatibleElements = false;
8065
8118
  }
@@ -8068,7 +8121,7 @@ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8068
8121
  return isCompatibleElements;
8069
8122
  }
8070
8123
  function haveCompatibleStaticParts(vnode, renderer) {
8071
- const { parts, owner } = vnode;
8124
+ const { parts } = vnode;
8072
8125
  if (shared.isUndefined(parts)) {
8073
8126
  return true;
8074
8127
  }
@@ -8079,11 +8132,11 @@ function haveCompatibleStaticParts(vnode, renderer) {
8079
8132
  for (const part of parts) {
8080
8133
  const { elm } = part;
8081
8134
  if (isVStaticPartElement(part)) {
8082
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8135
+ if (!isTypeElement(elm)) {
8083
8136
  return false;
8084
8137
  }
8085
8138
  const { data } = part;
8086
- const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
8139
+ const hasMatchingAttrs = validateAttrs(elm, data, renderer, () => true);
8087
8140
  // Explicitly skip hydration validation when static parts don't contain `style` or `className`.
8088
8141
  // This means the style/class attributes are either static or don't exist on the element and
8089
8142
  // cannot be affected by hydration.
@@ -8093,7 +8146,7 @@ function haveCompatibleStaticParts(vnode, renderer) {
8093
8146
  ? validateClassAttr(vnode, elm, data, renderer)
8094
8147
  : true;
8095
8148
  const hasMatchingStyleAttr = shouldValidateAttr(data, 'style')
8096
- ? validateStyleAttr(vnode, elm, data, renderer)
8149
+ ? validateStyleAttr(elm, data, renderer)
8097
8150
  : true;
8098
8151
  if (shared.isFalse(hasMatchingAttrs && hasMatchingClass && hasMatchingStyleAttr)) {
8099
8152
  return false;
@@ -8101,10 +8154,10 @@ function haveCompatibleStaticParts(vnode, renderer) {
8101
8154
  }
8102
8155
  else {
8103
8156
  // VStaticPartText
8104
- if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8157
+ if (!isTypeText(elm)) {
8105
8158
  return false;
8106
8159
  }
8107
- updateTextContent(elm, part, owner, renderer);
8160
+ updateTextContent(elm, part, renderer);
8108
8161
  }
8109
8162
  }
8110
8163
  return true;
@@ -8421,5 +8474,5 @@ exports.swapTemplate = swapTemplate;
8421
8474
  exports.track = track;
8422
8475
  exports.unwrap = unwrap;
8423
8476
  exports.wire = wire;
8424
- /** version: 8.12.3 */
8477
+ /** version: 8.12.5 */
8425
8478
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -248,6 +248,16 @@ function shouldBeFormAssociated(Ctor) {
248
248
  }
249
249
  return ctorFormAssociated && apiFeatureEnabled;
250
250
  }
251
+ // check if a property is in an object, and if the object throws an error merely because we are
252
+ // checking if the property exists, return false
253
+ function safeHasProp(obj, prop) {
254
+ try {
255
+ return prop in obj;
256
+ }
257
+ catch (_err) {
258
+ return false;
259
+ }
260
+ }
251
261
 
252
262
  /*
253
263
  * Copyright (c) 2024, Salesforce, Inc.
@@ -588,14 +598,14 @@ function componentValueObserved(vm, key, target = {}) {
588
598
  valueObserved(component, key);
589
599
  }
590
600
  // The portion of reactivity that's exposed to signals is to subscribe a callback to re-render the VM (templates).
591
- // We check check the following to ensure re-render is subscribed at the correct time.
601
+ // We check the following to ensure re-render is subscribed at the correct time.
592
602
  // 1. The template is currently being rendered (there is a template reactive observer)
593
603
  // 2. There was a call to a getter to access the signal (happens during vnode generation)
594
604
  if (lwcRuntimeFlags.ENABLE_EXPERIMENTAL_SIGNALS &&
595
605
  isObject(target) &&
596
606
  !isNull(target) &&
597
- 'value' in target &&
598
- 'subscribe' in target &&
607
+ safeHasProp(target, 'value') &&
608
+ safeHasProp(target, 'subscribe') &&
599
609
  isFunction$1(target.subscribe) &&
600
610
  isTrustedSignal(target) &&
601
611
  // Only subscribe if a template is being rendered by the engine
@@ -3964,15 +3974,6 @@ function safelySetProperty(setProperty, elm, key, value) {
3964
3974
  setProperty(elm, key, value);
3965
3975
  }
3966
3976
  }
3967
- /**
3968
- * Given two objects (likely either a string or a SanitizedHtmlContent object), return true if their
3969
- * string values are equivalent.
3970
- * @param first
3971
- * @param second
3972
- */
3973
- function isSanitizedHtmlContentEqual(first, second) {
3974
- return unwrapIfNecessary(first) === unwrapIfNecessary(second);
3975
- }
3976
3977
 
3977
3978
  /*
3978
3979
  * Copyright (c) 2018, salesforce.com, inc.
@@ -7525,6 +7526,78 @@ if (process.env.IS_BROWSER && isGlobalAriaPolyfillLoaded()) {
7525
7526
  }
7526
7527
  }
7527
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
+
7528
7601
  /*
7529
7602
  * Copyright (c) 2022, salesforce.com, inc.
7530
7603
  * All rights reserved.
@@ -7539,8 +7612,15 @@ function hydrateRoot(vm) {
7539
7612
  hasMismatch = false;
7540
7613
  runConnectedCallback(vm);
7541
7614
  hydrateVM(vm);
7542
- if (hasMismatch && process.env.NODE_ENV !== 'production') {
7543
- 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
+ }
7544
7624
  }
7545
7625
  }
7546
7626
  function hydrateVM(vm) {
@@ -7578,21 +7658,25 @@ function hydrateNode(node, vnode, renderer) {
7578
7658
  hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer);
7579
7659
  break;
7580
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
+ }
7581
7668
  return renderer.nextSibling(hydratedNode);
7582
7669
  }
7583
7670
  const NODE_VALUE_PROP = 'nodeValue';
7584
- function textNodeContentsAreEqual(node, vnode, renderer) {
7671
+ function validateTextNodeEquality(node, vnode, renderer) {
7585
7672
  const { getProperty } = renderer;
7586
7673
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7587
- if (nodeValue === vnode.text) {
7588
- return true;
7589
- }
7590
- // Special case for empty text nodes these are serialized differently on the server
7591
- // See https://github.com/salesforce/lwc/pull/2656
7592
- if (nodeValue === '\u200D' && vnode.text === '') {
7593
- 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);
7594
7679
  }
7595
- return false;
7596
7680
  }
7597
7681
  // The validationOptOut static property can be an array of attribute names.
7598
7682
  // Any attribute names specified in that array will not be validated, and the
@@ -7615,7 +7699,7 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7615
7699
  !isUndefined$1(optOutStaticProp) &&
7616
7700
  !isTrue(optOutStaticProp) &&
7617
7701
  !isValidArray) {
7618
- 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.');
7619
7703
  }
7620
7704
  return (attrName) => {
7621
7705
  // Component wants to opt out of all validation
@@ -7635,16 +7719,14 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7635
7719
  };
7636
7720
  }
7637
7721
  function hydrateText(node, vnode, renderer) {
7638
- if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7722
+ if (!isTypeText(node)) {
7639
7723
  return handleMismatch(node, vnode, renderer);
7640
7724
  }
7641
- return updateTextContent(node, vnode, vnode.owner, renderer);
7725
+ return updateTextContent(node, vnode, renderer);
7642
7726
  }
7643
- function updateTextContent(node, vnode, owner, renderer) {
7727
+ function updateTextContent(node, vnode, renderer) {
7644
7728
  if (process.env.NODE_ENV !== 'production') {
7645
- if (!textNodeContentsAreEqual(node, vnode, renderer)) {
7646
- logWarn('Hydration mismatch: text values do not match, will recover from the difference', owner);
7647
- }
7729
+ validateTextNodeEquality(node, vnode, renderer);
7648
7730
  }
7649
7731
  const { setText } = renderer;
7650
7732
  setText(node, vnode.text ?? null);
@@ -7652,14 +7734,14 @@ function updateTextContent(node, vnode, owner, renderer) {
7652
7734
  return node;
7653
7735
  }
7654
7736
  function hydrateComment(node, vnode, renderer) {
7655
- if (!hasCorrectNodeType(vnode, node, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
7737
+ if (!isTypeComment(node)) {
7656
7738
  return handleMismatch(node, vnode, renderer);
7657
7739
  }
7658
7740
  if (process.env.NODE_ENV !== 'production') {
7659
7741
  const { getProperty } = renderer;
7660
7742
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7661
7743
  if (nodeValue !== vnode.text) {
7662
- logWarn('Hydration mismatch: comment values do not match, will recover from the difference', vnode.owner);
7744
+ queueHydrationError('comment', nodeValue, vnode.text);
7663
7745
  }
7664
7746
  }
7665
7747
  const { setProperty } = renderer;
@@ -7670,11 +7752,12 @@ function hydrateComment(node, vnode, renderer) {
7670
7752
  return node;
7671
7753
  }
7672
7754
  function hydrateStaticElement(elm, vnode, renderer) {
7673
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7674
- !areCompatibleStaticNodes(vnode.fragment, elm, vnode, renderer)) {
7675
- 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);
7676
7759
  }
7677
- return hydrateStaticElementParts(elm, vnode, renderer);
7760
+ return handleMismatch(elm, vnode, renderer);
7678
7761
  }
7679
7762
  function hydrateStaticElementParts(elm, vnode, renderer) {
7680
7763
  const { parts } = vnode;
@@ -7697,8 +7780,7 @@ function hydrateFragment(elm, vnode, renderer) {
7697
7780
  return (vnode.elm = children[children.length - 1].elm);
7698
7781
  }
7699
7782
  function hydrateElement(elm, vnode, renderer) {
7700
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7701
- !isMatchingElement(vnode, elm, renderer)) {
7783
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer)) {
7702
7784
  return handleMismatch(elm, vnode, renderer);
7703
7785
  }
7704
7786
  vnode.elm = elm;
@@ -7711,17 +7793,17 @@ function hydrateElement(elm, vnode, renderer) {
7711
7793
  const { data: { props }, } = vnode;
7712
7794
  const { getProperty } = renderer;
7713
7795
  if (!isUndefined$1(props) && !isUndefined$1(props.innerHTML)) {
7714
- if (isSanitizedHtmlContentEqual(getProperty(elm, 'innerHTML'), props.innerHTML)) {
7796
+ const unwrappedServerInnerHTML = unwrapIfNecessary(getProperty(elm, 'innerHTML'));
7797
+ const unwrappedClientInnerHTML = unwrapIfNecessary(props.innerHTML);
7798
+ if (unwrappedServerInnerHTML === unwrappedClientInnerHTML) {
7715
7799
  // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization
7716
7800
  vnode.data = {
7717
7801
  ...vnode.data,
7718
7802
  props: cloneAndOmitKey(props, 'innerHTML'),
7719
7803
  };
7720
7804
  }
7721
- else {
7722
- if (process.env.NODE_ENV !== 'production') {
7723
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: innerHTML values do not match for element, will recover from the difference`, owner);
7724
- }
7805
+ else if (process.env.NODE_ENV !== 'production') {
7806
+ queueHydrationError('innerHTML', unwrappedServerInnerHTML, unwrappedClientInnerHTML);
7725
7807
  }
7726
7808
  }
7727
7809
  }
@@ -7744,8 +7826,7 @@ function hydrateCustomElement(elm, vnode, renderer) {
7744
7826
  //
7745
7827
  // Therefore, if validationOptOut is falsey or an array of strings, we need to
7746
7828
  // examine some or all of the custom element's attributes.
7747
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7748
- !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7829
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7749
7830
  return handleMismatch(elm, vnode, renderer);
7750
7831
  }
7751
7832
  const { sel, mode, ctor, owner } = vnode;
@@ -7781,9 +7862,13 @@ function hydrateChildren(node, children, parentNode, owner,
7781
7862
  // last node of the fragment. Hydration should not fail if a trailing sibling is
7782
7863
  // found in this case.
7783
7864
  expectAddlSiblings) {
7784
- let hasWarned = false;
7865
+ let mismatchedChildren = false;
7785
7866
  let nextNode = node;
7786
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;
7787
7872
  for (let i = 0; i < children.length; i++) {
7788
7873
  const childVnode = children[i];
7789
7874
  if (!isNull(childVnode)) {
@@ -7791,13 +7876,7 @@ expectAddlSiblings) {
7791
7876
  nextNode = hydrateNode(nextNode, childVnode, renderer);
7792
7877
  }
7793
7878
  else {
7794
- hasMismatch = true;
7795
- if (process.env.NODE_ENV !== 'production') {
7796
- if (!hasWarned) {
7797
- hasWarned = true;
7798
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Client produced more nodes than the server.`, owner);
7799
- }
7800
- }
7879
+ mismatchedChildren = true;
7801
7880
  mount(childVnode, parentNode, renderer, nextNode);
7802
7881
  nextNode = renderer.nextSibling(childVnode.type === 5 /* VNodeType.Fragment */ ? childVnode.trailing : childVnode.elm);
7803
7882
  }
@@ -7814,12 +7893,7 @@ expectAddlSiblings) {
7814
7893
  // rendered more nodes than the client.
7815
7894
  (!useCommentsForBookends || !expectAddlSiblings) &&
7816
7895
  nextNode) {
7817
- hasMismatch = true;
7818
- if (process.env.NODE_ENV !== 'production') {
7819
- if (!hasWarned) {
7820
- logWarn(`Hydration mismatch: incorrect number of rendered nodes. Server rendered more nodes than the client.`, owner);
7821
- }
7822
- }
7896
+ mismatchedChildren = true;
7823
7897
  // nextSibling is mostly harmless, and since we don't have
7824
7898
  // a good reference to what element to act upon, we instead
7825
7899
  // rely on the vm's associated renderer for navigating to the
@@ -7831,6 +7905,14 @@ expectAddlSiblings) {
7831
7905
  removeNode(current, parentNode, renderer);
7832
7906
  } while (nextNode);
7833
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
+ }
7834
7916
  }
7835
7917
  function handleMismatch(node, vnode, renderer) {
7836
7918
  hasMismatch = true;
@@ -7846,31 +7928,21 @@ function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7846
7928
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7847
7929
  applyRefs(vnode, vnode.owner);
7848
7930
  }
7849
- function hasCorrectNodeType(vnode, node, nodeType, renderer) {
7850
- const { getProperty } = renderer;
7851
- if (getProperty(node, 'nodeType') !== nodeType) {
7852
- if (process.env.NODE_ENV !== 'production') {
7853
- logWarn('Hydration mismatch: incorrect node type received', vnode.owner);
7854
- }
7855
- return false;
7856
- }
7857
- return true;
7858
- }
7859
7931
  function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true) {
7860
7932
  const { getProperty } = renderer;
7861
7933
  if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
7862
7934
  if (process.env.NODE_ENV !== 'production') {
7863
- logWarn(`Hydration mismatch: expecting element with tag "${vnode.sel.toLowerCase()}" but found "${getProperty(elm, 'tagName').toLowerCase()}".`, vnode.owner);
7935
+ queueHydrationError('node', elm);
7864
7936
  }
7865
7937
  return false;
7866
7938
  }
7867
7939
  const { data } = vnode;
7868
- const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7940
+ const hasCompatibleAttrs = validateAttrs(elm, data, renderer, shouldValidateAttr);
7869
7941
  const hasCompatibleClass = shouldValidateAttr('class')
7870
7942
  ? validateClassAttr(vnode, elm, data, renderer)
7871
7943
  : true;
7872
7944
  const hasCompatibleStyle = shouldValidateAttr('style')
7873
- ? validateStyleAttr(vnode, elm, data, renderer)
7945
+ ? validateStyleAttr(elm, data, renderer)
7874
7946
  : true;
7875
7947
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7876
7948
  }
@@ -7887,7 +7959,7 @@ function attributeValuesAreEqual(vnodeValue, value) {
7887
7959
  // In all other cases, the two values are not considered equal
7888
7960
  return false;
7889
7961
  }
7890
- function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7962
+ function validateAttrs(elm, data, renderer, shouldValidateAttr) {
7891
7963
  const { attrs = {} } = data;
7892
7964
  let nodesAreCompatible = true;
7893
7965
  // Validate attributes, though we could always recovery from those by running the update mods.
@@ -7900,8 +7972,7 @@ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7900
7972
  const elmAttrValue = getAttribute(elm, attrName);
7901
7973
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7902
7974
  if (process.env.NODE_ENV !== 'production') {
7903
- const { getProperty } = renderer;
7904
- 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));
7905
7976
  }
7906
7977
  nodesAreCompatible = false;
7907
7978
  }
@@ -7929,7 +8000,6 @@ function validateClassAttr(vnode, elm, data, renderer) {
7929
8000
  // classMap is never available on VStaticPartData so it can default to undefined
7930
8001
  // casting to prevent TS error.
7931
8002
  const { className, classMap } = data;
7932
- const { getProperty } = renderer;
7933
8003
  // ---------- Step 1: get the classes from the element and the vnode
7934
8004
  // Use a Set because we don't care to validate mismatches for 1) different ordering in SSR vs CSR, or 2)
7935
8005
  // duplicated class names. These don't have an effect on rendered styles.
@@ -7975,12 +8045,11 @@ function validateClassAttr(vnode, elm, data, renderer) {
7975
8045
  // ---------- Step 3: check for compatibility
7976
8046
  const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);
7977
8047
  if (process.env.NODE_ENV !== 'production' && !classesAreCompatible) {
7978
- const prettyPrint = (set) => JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(set)), ' '));
7979
- 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));
7980
8049
  }
7981
8050
  return classesAreCompatible;
7982
8051
  }
7983
- function validateStyleAttr(vnode, elm, data, renderer) {
8052
+ function validateStyleAttr(elm, data, renderer) {
7984
8053
  // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7985
8054
  const { style, styleDecls } = data;
7986
8055
  const { getAttribute } = renderer;
@@ -8014,49 +8083,33 @@ function validateStyleAttr(vnode, elm, data, renderer) {
8014
8083
  }
8015
8084
  vnodeStyle = ArrayJoin.call(expectedStyle, ' ');
8016
8085
  }
8017
- if (!nodesAreCompatible) {
8018
- if (process.env.NODE_ENV !== 'production') {
8019
- const { getProperty } = renderer;
8020
- logWarn(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "style" has different values, expected "${vnodeStyle}" but found "${elmStyle}".`, vnode.owner);
8021
- }
8086
+ if (process.env.NODE_ENV !== 'production' && !nodesAreCompatible) {
8087
+ queueHydrationError('attribute', prettyPrintAttribute('style', elmStyle), prettyPrintAttribute('style', vnodeStyle));
8022
8088
  }
8023
8089
  return nodesAreCompatible;
8024
8090
  }
8025
- function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8091
+ function areStaticElementsCompatible(clientElement, serverElement, vnode, renderer) {
8026
8092
  const { getProperty, getAttribute } = renderer;
8027
- if (getProperty(client, 'nodeType') === 3 /* EnvNodeTypes.TEXT */) {
8028
- if (!hasCorrectNodeType(vnode, ssr, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8029
- return false;
8030
- }
8031
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8032
- }
8033
- if (getProperty(client, 'nodeType') === 8 /* EnvNodeTypes.COMMENT */) {
8034
- if (!hasCorrectNodeType(vnode, ssr, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
8035
- return false;
8036
- }
8037
- return getProperty(client, NODE_VALUE_PROP) === getProperty(ssr, NODE_VALUE_PROP);
8038
- }
8039
- if (!hasCorrectNodeType(vnode, ssr, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8040
- return false;
8041
- }
8042
- const { owner, parts } = vnode;
8093
+ const { parts } = vnode;
8043
8094
  let isCompatibleElements = true;
8044
- if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
8095
+ if (getProperty(clientElement, 'tagName') !== getProperty(serverElement, 'tagName')) {
8045
8096
  if (process.env.NODE_ENV !== 'production') {
8046
- logWarn(`Hydration mismatch: expecting element with tag "${getProperty(client, 'tagName').toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, owner);
8097
+ queueHydrationError('node', serverElement);
8047
8098
  }
8048
8099
  return false;
8049
8100
  }
8050
- const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
8101
+ const clientAttrsNames = getProperty(clientElement, 'getAttributeNames').call(clientElement);
8051
8102
  clientAttrsNames.forEach((attrName) => {
8052
- if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
8103
+ const clientAttributeValue = getAttribute(clientElement, attrName);
8104
+ const serverAttributeValue = getAttribute(serverElement, attrName);
8105
+ if (clientAttributeValue !== serverAttributeValue) {
8053
8106
  // Check if the root element attributes have expressions, if it does then we need to delegate hydration
8054
8107
  // validation to haveCompatibleStaticParts.
8055
8108
  // Note if there are no parts then it is a fully static fragment.
8056
8109
  // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
8057
8110
  if (parts?.[0].partId !== 0) {
8058
8111
  if (process.env.NODE_ENV !== 'production') {
8059
- 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));
8060
8113
  }
8061
8114
  isCompatibleElements = false;
8062
8115
  }
@@ -8065,7 +8118,7 @@ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8065
8118
  return isCompatibleElements;
8066
8119
  }
8067
8120
  function haveCompatibleStaticParts(vnode, renderer) {
8068
- const { parts, owner } = vnode;
8121
+ const { parts } = vnode;
8069
8122
  if (isUndefined$1(parts)) {
8070
8123
  return true;
8071
8124
  }
@@ -8076,11 +8129,11 @@ function haveCompatibleStaticParts(vnode, renderer) {
8076
8129
  for (const part of parts) {
8077
8130
  const { elm } = part;
8078
8131
  if (isVStaticPartElement(part)) {
8079
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8132
+ if (!isTypeElement(elm)) {
8080
8133
  return false;
8081
8134
  }
8082
8135
  const { data } = part;
8083
- const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
8136
+ const hasMatchingAttrs = validateAttrs(elm, data, renderer, () => true);
8084
8137
  // Explicitly skip hydration validation when static parts don't contain `style` or `className`.
8085
8138
  // This means the style/class attributes are either static or don't exist on the element and
8086
8139
  // cannot be affected by hydration.
@@ -8090,7 +8143,7 @@ function haveCompatibleStaticParts(vnode, renderer) {
8090
8143
  ? validateClassAttr(vnode, elm, data, renderer)
8091
8144
  : true;
8092
8145
  const hasMatchingStyleAttr = shouldValidateAttr(data, 'style')
8093
- ? validateStyleAttr(vnode, elm, data, renderer)
8146
+ ? validateStyleAttr(elm, data, renderer)
8094
8147
  : true;
8095
8148
  if (isFalse(hasMatchingAttrs && hasMatchingClass && hasMatchingStyleAttr)) {
8096
8149
  return false;
@@ -8098,10 +8151,10 @@ function haveCompatibleStaticParts(vnode, renderer) {
8098
8151
  }
8099
8152
  else {
8100
8153
  // VStaticPartText
8101
- if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8154
+ if (!isTypeText(elm)) {
8102
8155
  return false;
8103
8156
  }
8104
- updateTextContent(elm, part, owner, renderer);
8157
+ updateTextContent(elm, part, renderer);
8105
8158
  }
8106
8159
  }
8107
8160
  return true;
@@ -8367,5 +8420,5 @@ function readonly(obj) {
8367
8420
  }
8368
8421
 
8369
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 };
8370
- /** version: 8.12.3 */
8423
+ /** version: 8.12.5 */
8371
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.3",
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.3",
50
- "@lwc/shared": "8.12.3",
51
- "@lwc/signals": "8.12.3"
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"