@lwc/engine-core 8.12.4 → 8.12.6-alpha.0

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 @@
1
+ export declare const defaultDefHTMLPropertyNames: string[];
@@ -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
@@ -1,5 +1,6 @@
1
+ import { LightningElement } from './base-lightning-element';
1
2
  import type { VM } from './vm';
2
- import type { LightningElement, LightningElementConstructor } from './base-lightning-element';
3
+ import type { LightningElementConstructor } from './base-lightning-element';
3
4
  import type { VNodes } from './vnodes';
4
5
  export declare let isInvokingRender: boolean;
5
6
  export declare let vmBeingConstructed: VM | null;
@@ -0,0 +1,6 @@
1
+ import { SanitizeHtmlContentHook } from './api';
2
+ interface OverridableHooksDef {
3
+ sanitizeHtmlContent: SanitizeHtmlContentHook;
4
+ }
5
+ export declare function setHooks(hooks: OverridableHooksDef): void;
6
+ export {};
@@ -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
@@ -2960,7 +2960,10 @@ function HTMLBridgeElementFactory(SuperClass, publicProperties, methods, observe
2960
2960
  const { observedAttributes: superObservedAttributes = [] } = SuperClass;
2961
2961
  const descriptors = shared.create(null);
2962
2962
  // present a hint message so that developers are aware that they have not decorated property with @api
2963
- if (process.env.NODE_ENV !== 'production') {
2963
+ // Note that we also don't do this in SSR because we cannot sniff for what props are declared on
2964
+ // HTMLElementPrototype, and it seems not worth it to have these dev-only warnings there, since
2965
+ // an `in` check could mistakenly assume that a prop is declared on a LightningElement prototype.
2966
+ if (process.env.NODE_ENV !== 'production' && process.env.IS_BROWSER) {
2964
2967
  // TODO [#3761]: enable for components that don't extend from LightningElement
2965
2968
  if (!shared.isUndefined(proto) && !shared.isNull(proto) && !hasCustomSuperClass) {
2966
2969
  const nonPublicPropertiesToWarnOn = new Set([
@@ -3977,15 +3980,6 @@ function safelySetProperty(setProperty, elm, key, value) {
3977
3980
  setProperty(elm, key, value);
3978
3981
  }
3979
3982
  }
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
3983
 
3990
3984
  /*
3991
3985
  * Copyright (c) 2018, salesforce.com, inc.
@@ -6484,7 +6478,10 @@ function invokeComponentConstructor(vm, Ctor) {
6484
6478
  // the "instanceof" operator would not work here since Locker Service provides its own
6485
6479
  // implementation of LightningElement, so we indirectly check if the base constructor is
6486
6480
  // invoked by accessing the component on the vm.
6487
- if (vmBeingConstructed.component !== result) {
6481
+ const isInvalidConstructor = lwcRuntimeFlags.ENABLE_LEGACY_LOCKER_SUPPORT
6482
+ ? vmBeingConstructed.component !== result
6483
+ : !(result instanceof LightningElement);
6484
+ if (isInvalidConstructor) {
6488
6485
  throw new TypeError('Invalid component constructor, the class should extend LightningElement.');
6489
6486
  }
6490
6487
  }
@@ -7538,6 +7535,78 @@ if (process.env.IS_BROWSER && isGlobalAriaPolyfillLoaded()) {
7538
7535
  }
7539
7536
  }
7540
7537
 
7538
+ /*
7539
+ * Copyright (c) 2024, Salesforce, Inc.
7540
+ * All rights reserved.
7541
+ * SPDX-License-Identifier: MIT
7542
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
7543
+ */
7544
+ // Errors that occured during the hydration process
7545
+ let hydrationErrors = [];
7546
+ /*
7547
+ Prints attributes as null or "value"
7548
+ */
7549
+ function prettyPrintAttribute(attribute, value) {
7550
+ assertNotProd(); // this method should never leak to prod
7551
+ return `${attribute}=${shared.isNull(value) || shared.isUndefined(value) ? value : `"${value}"`}`;
7552
+ }
7553
+ /*
7554
+ Sorts and stringifies classes
7555
+ */
7556
+ function prettyPrintClasses(classes) {
7557
+ assertNotProd(); // this method should never leak to prod
7558
+ const value = JSON.stringify(shared.ArrayJoin.call(shared.ArraySort.call(shared.ArrayFrom(classes)), ' '));
7559
+ return `class=${value}`;
7560
+ }
7561
+ /*
7562
+ Hydration errors occur before the source node has been fully hydrated,
7563
+ queue them so they can be logged later against the mounted node.
7564
+ */
7565
+ function queueHydrationError(type, serverRendered, clientExpected) {
7566
+ assertNotProd(); // this method should never leak to prod
7567
+ shared.ArrayPush.call(hydrationErrors, { type, serverRendered, clientExpected });
7568
+ }
7569
+ /*
7570
+ Flushes (logs) any queued errors after the source node has been mounted.
7571
+ */
7572
+ function flushHydrationErrors(source) {
7573
+ assertNotProd(); // this method should never leak to prod
7574
+ for (const hydrationError of hydrationErrors) {
7575
+ logHydrationWarning(`Hydration ${hydrationError.type} mismatch on:`, source, `\n- rendered on server:`, hydrationError.serverRendered, `\n- expected on client:`, hydrationError.clientExpected || source);
7576
+ }
7577
+ hydrationErrors = [];
7578
+ }
7579
+ function isTypeElement(node) {
7580
+ const isCorrectType = node?.nodeType === 1 /* EnvNodeTypes.ELEMENT */;
7581
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7582
+ queueHydrationError('node', node);
7583
+ }
7584
+ return isCorrectType;
7585
+ }
7586
+ function isTypeText(node) {
7587
+ const isCorrectType = node?.nodeType === 3 /* EnvNodeTypes.TEXT */;
7588
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7589
+ queueHydrationError('node', node);
7590
+ }
7591
+ return isCorrectType;
7592
+ }
7593
+ function isTypeComment(node) {
7594
+ const isCorrectType = node?.nodeType === 8 /* EnvNodeTypes.COMMENT */;
7595
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7596
+ queueHydrationError('node', node);
7597
+ }
7598
+ return isCorrectType;
7599
+ }
7600
+ /*
7601
+ logger.ts converts all args to a string, losing object referenences and has
7602
+ legacy bloat which would have meant more pathing.
7603
+ */
7604
+ function logHydrationWarning(...args) {
7605
+ assertNotProd(); // this method should never leak to prod
7606
+ /* eslint-disable-next-line no-console */
7607
+ console.warn('[LWC warn:', ...args);
7608
+ }
7609
+
7541
7610
  /*
7542
7611
  * Copyright (c) 2022, salesforce.com, inc.
7543
7612
  * All rights reserved.
@@ -7552,8 +7621,15 @@ function hydrateRoot(vm) {
7552
7621
  hasMismatch = false;
7553
7622
  runConnectedCallback(vm);
7554
7623
  hydrateVM(vm);
7555
- if (hasMismatch && process.env.NODE_ENV !== 'production') {
7556
- logWarn('Hydration completed with errors.', vm);
7624
+ if (process.env.NODE_ENV !== 'production') {
7625
+ /*
7626
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7627
+ 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.
7628
+ */
7629
+ flushHydrationErrors(vm.renderRoot);
7630
+ if (hasMismatch) {
7631
+ logHydrationWarning('Hydration completed with errors.');
7632
+ }
7557
7633
  }
7558
7634
  }
7559
7635
  function hydrateVM(vm) {
@@ -7591,21 +7667,25 @@ function hydrateNode(node, vnode, renderer) {
7591
7667
  hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer);
7592
7668
  break;
7593
7669
  }
7670
+ if (process.env.NODE_ENV !== 'production') {
7671
+ /*
7672
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7673
+ 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.
7674
+ */
7675
+ flushHydrationErrors(hydratedNode);
7676
+ }
7594
7677
  return renderer.nextSibling(hydratedNode);
7595
7678
  }
7596
7679
  const NODE_VALUE_PROP = 'nodeValue';
7597
- function textNodeContentsAreEqual(node, vnode, renderer) {
7680
+ function validateTextNodeEquality(node, vnode, renderer) {
7598
7681
  const { getProperty } = renderer;
7599
7682
  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;
7683
+ if (nodeValue !== vnode.text &&
7684
+ // Special case for empty text nodes – these are serialized differently on the server
7685
+ // See https://github.com/salesforce/lwc/pull/2656
7686
+ (nodeValue !== '\u200D' || vnode.text !== '')) {
7687
+ queueHydrationError('text content', nodeValue, vnode.text);
7607
7688
  }
7608
- return false;
7609
7689
  }
7610
7690
  // The validationOptOut static property can be an array of attribute names.
7611
7691
  // Any attribute names specified in that array will not be validated, and the
@@ -7628,7 +7708,7 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7628
7708
  !shared.isUndefined(optOutStaticProp) &&
7629
7709
  !shared.isTrue(optOutStaticProp) &&
7630
7710
  !isValidArray) {
7631
- logWarn('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7711
+ logHydrationWarning('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7632
7712
  }
7633
7713
  return (attrName) => {
7634
7714
  // Component wants to opt out of all validation
@@ -7648,16 +7728,14 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7648
7728
  };
7649
7729
  }
7650
7730
  function hydrateText(node, vnode, renderer) {
7651
- if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7731
+ if (!isTypeText(node)) {
7652
7732
  return handleMismatch(node, vnode, renderer);
7653
7733
  }
7654
- return updateTextContent(node, vnode, vnode.owner, renderer);
7734
+ return updateTextContent(node, vnode, renderer);
7655
7735
  }
7656
- function updateTextContent(node, vnode, owner, renderer) {
7736
+ function updateTextContent(node, vnode, renderer) {
7657
7737
  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
- }
7738
+ validateTextNodeEquality(node, vnode, renderer);
7661
7739
  }
7662
7740
  const { setText } = renderer;
7663
7741
  setText(node, vnode.text ?? null);
@@ -7665,14 +7743,14 @@ function updateTextContent(node, vnode, owner, renderer) {
7665
7743
  return node;
7666
7744
  }
7667
7745
  function hydrateComment(node, vnode, renderer) {
7668
- if (!hasCorrectNodeType(vnode, node, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
7746
+ if (!isTypeComment(node)) {
7669
7747
  return handleMismatch(node, vnode, renderer);
7670
7748
  }
7671
7749
  if (process.env.NODE_ENV !== 'production') {
7672
7750
  const { getProperty } = renderer;
7673
7751
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7674
7752
  if (nodeValue !== vnode.text) {
7675
- logWarn('Hydration mismatch: comment values do not match, will recover from the difference', vnode.owner);
7753
+ queueHydrationError('comment', nodeValue, vnode.text);
7676
7754
  }
7677
7755
  }
7678
7756
  const { setProperty } = renderer;
@@ -7683,11 +7761,12 @@ function hydrateComment(node, vnode, renderer) {
7683
7761
  return node;
7684
7762
  }
7685
7763
  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);
7764
+ if (isTypeElement(elm) &&
7765
+ isTypeElement(vnode.fragment) &&
7766
+ areStaticElementsCompatible(vnode.fragment, elm, vnode, renderer)) {
7767
+ return hydrateStaticElementParts(elm, vnode, renderer);
7689
7768
  }
7690
- return hydrateStaticElementParts(elm, vnode, renderer);
7769
+ return handleMismatch(elm, vnode, renderer);
7691
7770
  }
7692
7771
  function hydrateStaticElementParts(elm, vnode, renderer) {
7693
7772
  const { parts } = vnode;
@@ -7710,8 +7789,7 @@ function hydrateFragment(elm, vnode, renderer) {
7710
7789
  return (vnode.elm = children[children.length - 1].elm);
7711
7790
  }
7712
7791
  function hydrateElement(elm, vnode, renderer) {
7713
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7714
- !isMatchingElement(vnode, elm, renderer)) {
7792
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer)) {
7715
7793
  return handleMismatch(elm, vnode, renderer);
7716
7794
  }
7717
7795
  vnode.elm = elm;
@@ -7724,17 +7802,17 @@ function hydrateElement(elm, vnode, renderer) {
7724
7802
  const { data: { props }, } = vnode;
7725
7803
  const { getProperty } = renderer;
7726
7804
  if (!shared.isUndefined(props) && !shared.isUndefined(props.innerHTML)) {
7727
- if (isSanitizedHtmlContentEqual(getProperty(elm, 'innerHTML'), props.innerHTML)) {
7805
+ const unwrappedServerInnerHTML = unwrapIfNecessary(getProperty(elm, 'innerHTML'));
7806
+ const unwrappedClientInnerHTML = unwrapIfNecessary(props.innerHTML);
7807
+ if (unwrappedServerInnerHTML === unwrappedClientInnerHTML) {
7728
7808
  // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization
7729
7809
  vnode.data = {
7730
7810
  ...vnode.data,
7731
7811
  props: cloneAndOmitKey(props, 'innerHTML'),
7732
7812
  };
7733
7813
  }
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
- }
7814
+ else if (process.env.NODE_ENV !== 'production') {
7815
+ queueHydrationError('innerHTML', unwrappedServerInnerHTML, unwrappedClientInnerHTML);
7738
7816
  }
7739
7817
  }
7740
7818
  }
@@ -7757,8 +7835,7 @@ function hydrateCustomElement(elm, vnode, renderer) {
7757
7835
  //
7758
7836
  // Therefore, if validationOptOut is falsey or an array of strings, we need to
7759
7837
  // 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)) {
7838
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7762
7839
  return handleMismatch(elm, vnode, renderer);
7763
7840
  }
7764
7841
  const { sel, mode, ctor, owner } = vnode;
@@ -7794,9 +7871,13 @@ function hydrateChildren(node, children, parentNode, owner,
7794
7871
  // last node of the fragment. Hydration should not fail if a trailing sibling is
7795
7872
  // found in this case.
7796
7873
  expectAddlSiblings) {
7797
- let hasWarned = false;
7874
+ let mismatchedChildren = false;
7798
7875
  let nextNode = node;
7799
7876
  const { renderer } = owner;
7877
+ const { getChildNodes, cloneNode } = renderer;
7878
+ const serverNodes = process.env.NODE_ENV !== 'production'
7879
+ ? Array.from(getChildNodes(parentNode), (node) => cloneNode(node, true))
7880
+ : null;
7800
7881
  for (let i = 0; i < children.length; i++) {
7801
7882
  const childVnode = children[i];
7802
7883
  if (!shared.isNull(childVnode)) {
@@ -7804,13 +7885,7 @@ expectAddlSiblings) {
7804
7885
  nextNode = hydrateNode(nextNode, childVnode, renderer);
7805
7886
  }
7806
7887
  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
- }
7888
+ mismatchedChildren = true;
7814
7889
  mount(childVnode, parentNode, renderer, nextNode);
7815
7890
  nextNode = renderer.nextSibling(childVnode.type === 5 /* VNodeType.Fragment */ ? childVnode.trailing : childVnode.elm);
7816
7891
  }
@@ -7827,12 +7902,7 @@ expectAddlSiblings) {
7827
7902
  // rendered more nodes than the client.
7828
7903
  (!useCommentsForBookends || !expectAddlSiblings) &&
7829
7904
  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
- }
7905
+ mismatchedChildren = true;
7836
7906
  // nextSibling is mostly harmless, and since we don't have
7837
7907
  // a good reference to what element to act upon, we instead
7838
7908
  // rely on the vm's associated renderer for navigating to the
@@ -7844,6 +7914,14 @@ expectAddlSiblings) {
7844
7914
  removeNode(current, parentNode, renderer);
7845
7915
  } while (nextNode);
7846
7916
  }
7917
+ if (mismatchedChildren) {
7918
+ hasMismatch = true;
7919
+ // We can't know exactly which node(s) caused the delta, but we can provide context (parent) and the mismatched sets
7920
+ if (process.env.NODE_ENV !== 'production') {
7921
+ const clientNodes = shared.ArrayMap.call(children, (c) => c?.elm);
7922
+ queueHydrationError('child node', serverNodes, clientNodes);
7923
+ }
7924
+ }
7847
7925
  }
7848
7926
  function handleMismatch(node, vnode, renderer) {
7849
7927
  hasMismatch = true;
@@ -7859,31 +7937,21 @@ function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7859
7937
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7860
7938
  applyRefs(vnode, vnode.owner);
7861
7939
  }
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
7940
  function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true) {
7873
7941
  const { getProperty } = renderer;
7874
7942
  if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
7875
7943
  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);
7944
+ queueHydrationError('node', elm);
7877
7945
  }
7878
7946
  return false;
7879
7947
  }
7880
7948
  const { data } = vnode;
7881
- const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7949
+ const hasCompatibleAttrs = validateAttrs(elm, data, renderer, shouldValidateAttr);
7882
7950
  const hasCompatibleClass = shouldValidateAttr('class')
7883
7951
  ? validateClassAttr(vnode, elm, data, renderer)
7884
7952
  : true;
7885
7953
  const hasCompatibleStyle = shouldValidateAttr('style')
7886
- ? validateStyleAttr(vnode, elm, data, renderer)
7954
+ ? validateStyleAttr(elm, data, renderer)
7887
7955
  : true;
7888
7956
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7889
7957
  }
@@ -7900,7 +7968,7 @@ function attributeValuesAreEqual(vnodeValue, value) {
7900
7968
  // In all other cases, the two values are not considered equal
7901
7969
  return false;
7902
7970
  }
7903
- function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7971
+ function validateAttrs(elm, data, renderer, shouldValidateAttr) {
7904
7972
  const { attrs = {} } = data;
7905
7973
  let nodesAreCompatible = true;
7906
7974
  // Validate attributes, though we could always recovery from those by running the update mods.
@@ -7913,8 +7981,7 @@ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7913
7981
  const elmAttrValue = getAttribute(elm, attrName);
7914
7982
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7915
7983
  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);
7984
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, elmAttrValue), prettyPrintAttribute(attrName, attrValue));
7918
7985
  }
7919
7986
  nodesAreCompatible = false;
7920
7987
  }
@@ -7942,7 +8009,6 @@ function validateClassAttr(vnode, elm, data, renderer) {
7942
8009
  // classMap is never available on VStaticPartData so it can default to undefined
7943
8010
  // casting to prevent TS error.
7944
8011
  const { className, classMap } = data;
7945
- const { getProperty } = renderer;
7946
8012
  // ---------- Step 1: get the classes from the element and the vnode
7947
8013
  // Use a Set because we don't care to validate mismatches for 1) different ordering in SSR vs CSR, or 2)
7948
8014
  // duplicated class names. These don't have an effect on rendered styles.
@@ -7988,12 +8054,11 @@ function validateClassAttr(vnode, elm, data, renderer) {
7988
8054
  // ---------- Step 3: check for compatibility
7989
8055
  const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);
7990
8056
  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);
8057
+ queueHydrationError('attribute', prettyPrintClasses(elmClasses), prettyPrintClasses(vnodeClasses));
7993
8058
  }
7994
8059
  return classesAreCompatible;
7995
8060
  }
7996
- function validateStyleAttr(vnode, elm, data, renderer) {
8061
+ function validateStyleAttr(elm, data, renderer) {
7997
8062
  // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7998
8063
  const { style, styleDecls } = data;
7999
8064
  const { getAttribute } = renderer;
@@ -8027,49 +8092,33 @@ function validateStyleAttr(vnode, elm, data, renderer) {
8027
8092
  }
8028
8093
  vnodeStyle = shared.ArrayJoin.call(expectedStyle, ' ');
8029
8094
  }
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
- }
8095
+ if (process.env.NODE_ENV !== 'production' && !nodesAreCompatible) {
8096
+ queueHydrationError('attribute', prettyPrintAttribute('style', elmStyle), prettyPrintAttribute('style', vnodeStyle));
8035
8097
  }
8036
8098
  return nodesAreCompatible;
8037
8099
  }
8038
- function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8100
+ function areStaticElementsCompatible(clientElement, serverElement, vnode, renderer) {
8039
8101
  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;
8102
+ const { parts } = vnode;
8056
8103
  let isCompatibleElements = true;
8057
- if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
8104
+ if (getProperty(clientElement, 'tagName') !== getProperty(serverElement, 'tagName')) {
8058
8105
  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);
8106
+ queueHydrationError('node', serverElement);
8060
8107
  }
8061
8108
  return false;
8062
8109
  }
8063
- const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
8110
+ const clientAttrsNames = getProperty(clientElement, 'getAttributeNames').call(clientElement);
8064
8111
  clientAttrsNames.forEach((attrName) => {
8065
- if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
8112
+ const clientAttributeValue = getAttribute(clientElement, attrName);
8113
+ const serverAttributeValue = getAttribute(serverElement, attrName);
8114
+ if (clientAttributeValue !== serverAttributeValue) {
8066
8115
  // Check if the root element attributes have expressions, if it does then we need to delegate hydration
8067
8116
  // validation to haveCompatibleStaticParts.
8068
8117
  // Note if there are no parts then it is a fully static fragment.
8069
8118
  // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
8070
8119
  if (parts?.[0].partId !== 0) {
8071
8120
  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);
8121
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, serverAttributeValue), prettyPrintAttribute(attrName, clientAttributeValue));
8073
8122
  }
8074
8123
  isCompatibleElements = false;
8075
8124
  }
@@ -8078,7 +8127,7 @@ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8078
8127
  return isCompatibleElements;
8079
8128
  }
8080
8129
  function haveCompatibleStaticParts(vnode, renderer) {
8081
- const { parts, owner } = vnode;
8130
+ const { parts } = vnode;
8082
8131
  if (shared.isUndefined(parts)) {
8083
8132
  return true;
8084
8133
  }
@@ -8089,11 +8138,11 @@ function haveCompatibleStaticParts(vnode, renderer) {
8089
8138
  for (const part of parts) {
8090
8139
  const { elm } = part;
8091
8140
  if (isVStaticPartElement(part)) {
8092
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8141
+ if (!isTypeElement(elm)) {
8093
8142
  return false;
8094
8143
  }
8095
8144
  const { data } = part;
8096
- const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
8145
+ const hasMatchingAttrs = validateAttrs(elm, data, renderer, () => true);
8097
8146
  // Explicitly skip hydration validation when static parts don't contain `style` or `className`.
8098
8147
  // This means the style/class attributes are either static or don't exist on the element and
8099
8148
  // cannot be affected by hydration.
@@ -8103,7 +8152,7 @@ function haveCompatibleStaticParts(vnode, renderer) {
8103
8152
  ? validateClassAttr(vnode, elm, data, renderer)
8104
8153
  : true;
8105
8154
  const hasMatchingStyleAttr = shouldValidateAttr(data, 'style')
8106
- ? validateStyleAttr(vnode, elm, data, renderer)
8155
+ ? validateStyleAttr(elm, data, renderer)
8107
8156
  : true;
8108
8157
  if (shared.isFalse(hasMatchingAttrs && hasMatchingClass && hasMatchingStyleAttr)) {
8109
8158
  return false;
@@ -8111,10 +8160,10 @@ function haveCompatibleStaticParts(vnode, renderer) {
8111
8160
  }
8112
8161
  else {
8113
8162
  // VStaticPartText
8114
- if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8163
+ if (!isTypeText(elm)) {
8115
8164
  return false;
8116
8165
  }
8117
- updateTextContent(elm, part, owner, renderer);
8166
+ updateTextContent(elm, part, renderer);
8118
8167
  }
8119
8168
  }
8120
8169
  return true;
@@ -8431,5 +8480,5 @@ exports.swapTemplate = swapTemplate;
8431
8480
  exports.track = track;
8432
8481
  exports.unwrap = unwrap;
8433
8482
  exports.wire = wire;
8434
- /** version: 8.12.4 */
8483
+ /** version: 8.12.5 */
8435
8484
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -2957,7 +2957,10 @@ function HTMLBridgeElementFactory(SuperClass, publicProperties, methods, observe
2957
2957
  const { observedAttributes: superObservedAttributes = [] } = SuperClass;
2958
2958
  const descriptors = create(null);
2959
2959
  // present a hint message so that developers are aware that they have not decorated property with @api
2960
- if (process.env.NODE_ENV !== 'production') {
2960
+ // Note that we also don't do this in SSR because we cannot sniff for what props are declared on
2961
+ // HTMLElementPrototype, and it seems not worth it to have these dev-only warnings there, since
2962
+ // an `in` check could mistakenly assume that a prop is declared on a LightningElement prototype.
2963
+ if (process.env.NODE_ENV !== 'production' && process.env.IS_BROWSER) {
2961
2964
  // TODO [#3761]: enable for components that don't extend from LightningElement
2962
2965
  if (!isUndefined$1(proto) && !isNull(proto) && !hasCustomSuperClass) {
2963
2966
  const nonPublicPropertiesToWarnOn = new Set([
@@ -3974,15 +3977,6 @@ function safelySetProperty(setProperty, elm, key, value) {
3974
3977
  setProperty(elm, key, value);
3975
3978
  }
3976
3979
  }
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
3980
 
3987
3981
  /*
3988
3982
  * Copyright (c) 2018, salesforce.com, inc.
@@ -6481,7 +6475,10 @@ function invokeComponentConstructor(vm, Ctor) {
6481
6475
  // the "instanceof" operator would not work here since Locker Service provides its own
6482
6476
  // implementation of LightningElement, so we indirectly check if the base constructor is
6483
6477
  // invoked by accessing the component on the vm.
6484
- if (vmBeingConstructed.component !== result) {
6478
+ const isInvalidConstructor = lwcRuntimeFlags.ENABLE_LEGACY_LOCKER_SUPPORT
6479
+ ? vmBeingConstructed.component !== result
6480
+ : !(result instanceof LightningElement);
6481
+ if (isInvalidConstructor) {
6485
6482
  throw new TypeError('Invalid component constructor, the class should extend LightningElement.');
6486
6483
  }
6487
6484
  }
@@ -7535,6 +7532,78 @@ if (process.env.IS_BROWSER && isGlobalAriaPolyfillLoaded()) {
7535
7532
  }
7536
7533
  }
7537
7534
 
7535
+ /*
7536
+ * Copyright (c) 2024, Salesforce, Inc.
7537
+ * All rights reserved.
7538
+ * SPDX-License-Identifier: MIT
7539
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
7540
+ */
7541
+ // Errors that occured during the hydration process
7542
+ let hydrationErrors = [];
7543
+ /*
7544
+ Prints attributes as null or "value"
7545
+ */
7546
+ function prettyPrintAttribute(attribute, value) {
7547
+ assertNotProd(); // this method should never leak to prod
7548
+ return `${attribute}=${isNull(value) || isUndefined$1(value) ? value : `"${value}"`}`;
7549
+ }
7550
+ /*
7551
+ Sorts and stringifies classes
7552
+ */
7553
+ function prettyPrintClasses(classes) {
7554
+ assertNotProd(); // this method should never leak to prod
7555
+ const value = JSON.stringify(ArrayJoin.call(ArraySort.call(ArrayFrom(classes)), ' '));
7556
+ return `class=${value}`;
7557
+ }
7558
+ /*
7559
+ Hydration errors occur before the source node has been fully hydrated,
7560
+ queue them so they can be logged later against the mounted node.
7561
+ */
7562
+ function queueHydrationError(type, serverRendered, clientExpected) {
7563
+ assertNotProd(); // this method should never leak to prod
7564
+ ArrayPush$1.call(hydrationErrors, { type, serverRendered, clientExpected });
7565
+ }
7566
+ /*
7567
+ Flushes (logs) any queued errors after the source node has been mounted.
7568
+ */
7569
+ function flushHydrationErrors(source) {
7570
+ assertNotProd(); // this method should never leak to prod
7571
+ for (const hydrationError of hydrationErrors) {
7572
+ logHydrationWarning(`Hydration ${hydrationError.type} mismatch on:`, source, `\n- rendered on server:`, hydrationError.serverRendered, `\n- expected on client:`, hydrationError.clientExpected || source);
7573
+ }
7574
+ hydrationErrors = [];
7575
+ }
7576
+ function isTypeElement(node) {
7577
+ const isCorrectType = node?.nodeType === 1 /* EnvNodeTypes.ELEMENT */;
7578
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7579
+ queueHydrationError('node', node);
7580
+ }
7581
+ return isCorrectType;
7582
+ }
7583
+ function isTypeText(node) {
7584
+ const isCorrectType = node?.nodeType === 3 /* EnvNodeTypes.TEXT */;
7585
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7586
+ queueHydrationError('node', node);
7587
+ }
7588
+ return isCorrectType;
7589
+ }
7590
+ function isTypeComment(node) {
7591
+ const isCorrectType = node?.nodeType === 8 /* EnvNodeTypes.COMMENT */;
7592
+ if (process.env.NODE_ENV !== 'production' && !isCorrectType) {
7593
+ queueHydrationError('node', node);
7594
+ }
7595
+ return isCorrectType;
7596
+ }
7597
+ /*
7598
+ logger.ts converts all args to a string, losing object referenences and has
7599
+ legacy bloat which would have meant more pathing.
7600
+ */
7601
+ function logHydrationWarning(...args) {
7602
+ assertNotProd(); // this method should never leak to prod
7603
+ /* eslint-disable-next-line no-console */
7604
+ console.warn('[LWC warn:', ...args);
7605
+ }
7606
+
7538
7607
  /*
7539
7608
  * Copyright (c) 2022, salesforce.com, inc.
7540
7609
  * All rights reserved.
@@ -7549,8 +7618,15 @@ function hydrateRoot(vm) {
7549
7618
  hasMismatch = false;
7550
7619
  runConnectedCallback(vm);
7551
7620
  hydrateVM(vm);
7552
- if (hasMismatch && process.env.NODE_ENV !== 'production') {
7553
- logWarn('Hydration completed with errors.', vm);
7621
+ if (process.env.NODE_ENV !== 'production') {
7622
+ /*
7623
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7624
+ 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.
7625
+ */
7626
+ flushHydrationErrors(vm.renderRoot);
7627
+ if (hasMismatch) {
7628
+ logHydrationWarning('Hydration completed with errors.');
7629
+ }
7554
7630
  }
7555
7631
  }
7556
7632
  function hydrateVM(vm) {
@@ -7588,21 +7664,25 @@ function hydrateNode(node, vnode, renderer) {
7588
7664
  hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer);
7589
7665
  break;
7590
7666
  }
7667
+ if (process.env.NODE_ENV !== 'production') {
7668
+ /*
7669
+ Errors are queued as they occur and then logged with the source element once it has been hydrated and mounted to the DOM.
7670
+ 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.
7671
+ */
7672
+ flushHydrationErrors(hydratedNode);
7673
+ }
7591
7674
  return renderer.nextSibling(hydratedNode);
7592
7675
  }
7593
7676
  const NODE_VALUE_PROP = 'nodeValue';
7594
- function textNodeContentsAreEqual(node, vnode, renderer) {
7677
+ function validateTextNodeEquality(node, vnode, renderer) {
7595
7678
  const { getProperty } = renderer;
7596
7679
  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;
7680
+ if (nodeValue !== vnode.text &&
7681
+ // Special case for empty text nodes – these are serialized differently on the server
7682
+ // See https://github.com/salesforce/lwc/pull/2656
7683
+ (nodeValue !== '\u200D' || vnode.text !== '')) {
7684
+ queueHydrationError('text content', nodeValue, vnode.text);
7604
7685
  }
7605
- return false;
7606
7686
  }
7607
7687
  // The validationOptOut static property can be an array of attribute names.
7608
7688
  // Any attribute names specified in that array will not be validated, and the
@@ -7625,7 +7705,7 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7625
7705
  !isUndefined$1(optOutStaticProp) &&
7626
7706
  !isTrue(optOutStaticProp) &&
7627
7707
  !isValidArray) {
7628
- logWarn('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7708
+ logHydrationWarning('`validationOptOut` must be `true` or an array of attributes that should not be validated.');
7629
7709
  }
7630
7710
  return (attrName) => {
7631
7711
  // Component wants to opt out of all validation
@@ -7645,16 +7725,14 @@ function getValidationPredicate(elm, renderer, optOutStaticProp) {
7645
7725
  };
7646
7726
  }
7647
7727
  function hydrateText(node, vnode, renderer) {
7648
- if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7728
+ if (!isTypeText(node)) {
7649
7729
  return handleMismatch(node, vnode, renderer);
7650
7730
  }
7651
- return updateTextContent(node, vnode, vnode.owner, renderer);
7731
+ return updateTextContent(node, vnode, renderer);
7652
7732
  }
7653
- function updateTextContent(node, vnode, owner, renderer) {
7733
+ function updateTextContent(node, vnode, renderer) {
7654
7734
  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
- }
7735
+ validateTextNodeEquality(node, vnode, renderer);
7658
7736
  }
7659
7737
  const { setText } = renderer;
7660
7738
  setText(node, vnode.text ?? null);
@@ -7662,14 +7740,14 @@ function updateTextContent(node, vnode, owner, renderer) {
7662
7740
  return node;
7663
7741
  }
7664
7742
  function hydrateComment(node, vnode, renderer) {
7665
- if (!hasCorrectNodeType(vnode, node, 8 /* EnvNodeTypes.COMMENT */, renderer)) {
7743
+ if (!isTypeComment(node)) {
7666
7744
  return handleMismatch(node, vnode, renderer);
7667
7745
  }
7668
7746
  if (process.env.NODE_ENV !== 'production') {
7669
7747
  const { getProperty } = renderer;
7670
7748
  const nodeValue = getProperty(node, NODE_VALUE_PROP);
7671
7749
  if (nodeValue !== vnode.text) {
7672
- logWarn('Hydration mismatch: comment values do not match, will recover from the difference', vnode.owner);
7750
+ queueHydrationError('comment', nodeValue, vnode.text);
7673
7751
  }
7674
7752
  }
7675
7753
  const { setProperty } = renderer;
@@ -7680,11 +7758,12 @@ function hydrateComment(node, vnode, renderer) {
7680
7758
  return node;
7681
7759
  }
7682
7760
  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);
7761
+ if (isTypeElement(elm) &&
7762
+ isTypeElement(vnode.fragment) &&
7763
+ areStaticElementsCompatible(vnode.fragment, elm, vnode, renderer)) {
7764
+ return hydrateStaticElementParts(elm, vnode, renderer);
7686
7765
  }
7687
- return hydrateStaticElementParts(elm, vnode, renderer);
7766
+ return handleMismatch(elm, vnode, renderer);
7688
7767
  }
7689
7768
  function hydrateStaticElementParts(elm, vnode, renderer) {
7690
7769
  const { parts } = vnode;
@@ -7707,8 +7786,7 @@ function hydrateFragment(elm, vnode, renderer) {
7707
7786
  return (vnode.elm = children[children.length - 1].elm);
7708
7787
  }
7709
7788
  function hydrateElement(elm, vnode, renderer) {
7710
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
7711
- !isMatchingElement(vnode, elm, renderer)) {
7789
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer)) {
7712
7790
  return handleMismatch(elm, vnode, renderer);
7713
7791
  }
7714
7792
  vnode.elm = elm;
@@ -7721,17 +7799,17 @@ function hydrateElement(elm, vnode, renderer) {
7721
7799
  const { data: { props }, } = vnode;
7722
7800
  const { getProperty } = renderer;
7723
7801
  if (!isUndefined$1(props) && !isUndefined$1(props.innerHTML)) {
7724
- if (isSanitizedHtmlContentEqual(getProperty(elm, 'innerHTML'), props.innerHTML)) {
7802
+ const unwrappedServerInnerHTML = unwrapIfNecessary(getProperty(elm, 'innerHTML'));
7803
+ const unwrappedClientInnerHTML = unwrapIfNecessary(props.innerHTML);
7804
+ if (unwrappedServerInnerHTML === unwrappedClientInnerHTML) {
7725
7805
  // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization
7726
7806
  vnode.data = {
7727
7807
  ...vnode.data,
7728
7808
  props: cloneAndOmitKey(props, 'innerHTML'),
7729
7809
  };
7730
7810
  }
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
- }
7811
+ else if (process.env.NODE_ENV !== 'production') {
7812
+ queueHydrationError('innerHTML', unwrappedServerInnerHTML, unwrappedClientInnerHTML);
7735
7813
  }
7736
7814
  }
7737
7815
  }
@@ -7754,8 +7832,7 @@ function hydrateCustomElement(elm, vnode, renderer) {
7754
7832
  //
7755
7833
  // Therefore, if validationOptOut is falsey or an array of strings, we need to
7756
7834
  // 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)) {
7835
+ if (!isTypeElement(elm) || !isMatchingElement(vnode, elm, renderer, shouldValidateAttr)) {
7759
7836
  return handleMismatch(elm, vnode, renderer);
7760
7837
  }
7761
7838
  const { sel, mode, ctor, owner } = vnode;
@@ -7791,9 +7868,13 @@ function hydrateChildren(node, children, parentNode, owner,
7791
7868
  // last node of the fragment. Hydration should not fail if a trailing sibling is
7792
7869
  // found in this case.
7793
7870
  expectAddlSiblings) {
7794
- let hasWarned = false;
7871
+ let mismatchedChildren = false;
7795
7872
  let nextNode = node;
7796
7873
  const { renderer } = owner;
7874
+ const { getChildNodes, cloneNode } = renderer;
7875
+ const serverNodes = process.env.NODE_ENV !== 'production'
7876
+ ? Array.from(getChildNodes(parentNode), (node) => cloneNode(node, true))
7877
+ : null;
7797
7878
  for (let i = 0; i < children.length; i++) {
7798
7879
  const childVnode = children[i];
7799
7880
  if (!isNull(childVnode)) {
@@ -7801,13 +7882,7 @@ expectAddlSiblings) {
7801
7882
  nextNode = hydrateNode(nextNode, childVnode, renderer);
7802
7883
  }
7803
7884
  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
- }
7885
+ mismatchedChildren = true;
7811
7886
  mount(childVnode, parentNode, renderer, nextNode);
7812
7887
  nextNode = renderer.nextSibling(childVnode.type === 5 /* VNodeType.Fragment */ ? childVnode.trailing : childVnode.elm);
7813
7888
  }
@@ -7824,12 +7899,7 @@ expectAddlSiblings) {
7824
7899
  // rendered more nodes than the client.
7825
7900
  (!useCommentsForBookends || !expectAddlSiblings) &&
7826
7901
  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
- }
7902
+ mismatchedChildren = true;
7833
7903
  // nextSibling is mostly harmless, and since we don't have
7834
7904
  // a good reference to what element to act upon, we instead
7835
7905
  // rely on the vm's associated renderer for navigating to the
@@ -7841,6 +7911,14 @@ expectAddlSiblings) {
7841
7911
  removeNode(current, parentNode, renderer);
7842
7912
  } while (nextNode);
7843
7913
  }
7914
+ if (mismatchedChildren) {
7915
+ hasMismatch = true;
7916
+ // We can't know exactly which node(s) caused the delta, but we can provide context (parent) and the mismatched sets
7917
+ if (process.env.NODE_ENV !== 'production') {
7918
+ const clientNodes = ArrayMap.call(children, (c) => c?.elm);
7919
+ queueHydrationError('child node', serverNodes, clientNodes);
7920
+ }
7921
+ }
7844
7922
  }
7845
7923
  function handleMismatch(node, vnode, renderer) {
7846
7924
  hasMismatch = true;
@@ -7856,31 +7934,21 @@ function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7856
7934
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7857
7935
  applyRefs(vnode, vnode.owner);
7858
7936
  }
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
7937
  function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true) {
7870
7938
  const { getProperty } = renderer;
7871
7939
  if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) {
7872
7940
  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);
7941
+ queueHydrationError('node', elm);
7874
7942
  }
7875
7943
  return false;
7876
7944
  }
7877
7945
  const { data } = vnode;
7878
- const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7946
+ const hasCompatibleAttrs = validateAttrs(elm, data, renderer, shouldValidateAttr);
7879
7947
  const hasCompatibleClass = shouldValidateAttr('class')
7880
7948
  ? validateClassAttr(vnode, elm, data, renderer)
7881
7949
  : true;
7882
7950
  const hasCompatibleStyle = shouldValidateAttr('style')
7883
- ? validateStyleAttr(vnode, elm, data, renderer)
7951
+ ? validateStyleAttr(elm, data, renderer)
7884
7952
  : true;
7885
7953
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7886
7954
  }
@@ -7897,7 +7965,7 @@ function attributeValuesAreEqual(vnodeValue, value) {
7897
7965
  // In all other cases, the two values are not considered equal
7898
7966
  return false;
7899
7967
  }
7900
- function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7968
+ function validateAttrs(elm, data, renderer, shouldValidateAttr) {
7901
7969
  const { attrs = {} } = data;
7902
7970
  let nodesAreCompatible = true;
7903
7971
  // Validate attributes, though we could always recovery from those by running the update mods.
@@ -7910,8 +7978,7 @@ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7910
7978
  const elmAttrValue = getAttribute(elm, attrName);
7911
7979
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7912
7980
  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);
7981
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, elmAttrValue), prettyPrintAttribute(attrName, attrValue));
7915
7982
  }
7916
7983
  nodesAreCompatible = false;
7917
7984
  }
@@ -7939,7 +8006,6 @@ function validateClassAttr(vnode, elm, data, renderer) {
7939
8006
  // classMap is never available on VStaticPartData so it can default to undefined
7940
8007
  // casting to prevent TS error.
7941
8008
  const { className, classMap } = data;
7942
- const { getProperty } = renderer;
7943
8009
  // ---------- Step 1: get the classes from the element and the vnode
7944
8010
  // Use a Set because we don't care to validate mismatches for 1) different ordering in SSR vs CSR, or 2)
7945
8011
  // duplicated class names. These don't have an effect on rendered styles.
@@ -7985,12 +8051,11 @@ function validateClassAttr(vnode, elm, data, renderer) {
7985
8051
  // ---------- Step 3: check for compatibility
7986
8052
  const classesAreCompatible = checkClassesCompatibility(vnodeClasses, elmClasses);
7987
8053
  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);
8054
+ queueHydrationError('attribute', prettyPrintClasses(elmClasses), prettyPrintClasses(vnodeClasses));
7990
8055
  }
7991
8056
  return classesAreCompatible;
7992
8057
  }
7993
- function validateStyleAttr(vnode, elm, data, renderer) {
8058
+ function validateStyleAttr(elm, data, renderer) {
7994
8059
  // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7995
8060
  const { style, styleDecls } = data;
7996
8061
  const { getAttribute } = renderer;
@@ -8024,49 +8089,33 @@ function validateStyleAttr(vnode, elm, data, renderer) {
8024
8089
  }
8025
8090
  vnodeStyle = ArrayJoin.call(expectedStyle, ' ');
8026
8091
  }
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
- }
8092
+ if (process.env.NODE_ENV !== 'production' && !nodesAreCompatible) {
8093
+ queueHydrationError('attribute', prettyPrintAttribute('style', elmStyle), prettyPrintAttribute('style', vnodeStyle));
8032
8094
  }
8033
8095
  return nodesAreCompatible;
8034
8096
  }
8035
- function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8097
+ function areStaticElementsCompatible(clientElement, serverElement, vnode, renderer) {
8036
8098
  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;
8099
+ const { parts } = vnode;
8053
8100
  let isCompatibleElements = true;
8054
- if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
8101
+ if (getProperty(clientElement, 'tagName') !== getProperty(serverElement, 'tagName')) {
8055
8102
  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);
8103
+ queueHydrationError('node', serverElement);
8057
8104
  }
8058
8105
  return false;
8059
8106
  }
8060
- const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
8107
+ const clientAttrsNames = getProperty(clientElement, 'getAttributeNames').call(clientElement);
8061
8108
  clientAttrsNames.forEach((attrName) => {
8062
- if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
8109
+ const clientAttributeValue = getAttribute(clientElement, attrName);
8110
+ const serverAttributeValue = getAttribute(serverElement, attrName);
8111
+ if (clientAttributeValue !== serverAttributeValue) {
8063
8112
  // Check if the root element attributes have expressions, if it does then we need to delegate hydration
8064
8113
  // validation to haveCompatibleStaticParts.
8065
8114
  // Note if there are no parts then it is a fully static fragment.
8066
8115
  // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
8067
8116
  if (parts?.[0].partId !== 0) {
8068
8117
  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);
8118
+ queueHydrationError('attribute', prettyPrintAttribute(attrName, serverAttributeValue), prettyPrintAttribute(attrName, clientAttributeValue));
8070
8119
  }
8071
8120
  isCompatibleElements = false;
8072
8121
  }
@@ -8075,7 +8124,7 @@ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
8075
8124
  return isCompatibleElements;
8076
8125
  }
8077
8126
  function haveCompatibleStaticParts(vnode, renderer) {
8078
- const { parts, owner } = vnode;
8127
+ const { parts } = vnode;
8079
8128
  if (isUndefined$1(parts)) {
8080
8129
  return true;
8081
8130
  }
@@ -8086,11 +8135,11 @@ function haveCompatibleStaticParts(vnode, renderer) {
8086
8135
  for (const part of parts) {
8087
8136
  const { elm } = part;
8088
8137
  if (isVStaticPartElement(part)) {
8089
- if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
8138
+ if (!isTypeElement(elm)) {
8090
8139
  return false;
8091
8140
  }
8092
8141
  const { data } = part;
8093
- const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
8142
+ const hasMatchingAttrs = validateAttrs(elm, data, renderer, () => true);
8094
8143
  // Explicitly skip hydration validation when static parts don't contain `style` or `className`.
8095
8144
  // This means the style/class attributes are either static or don't exist on the element and
8096
8145
  // cannot be affected by hydration.
@@ -8100,7 +8149,7 @@ function haveCompatibleStaticParts(vnode, renderer) {
8100
8149
  ? validateClassAttr(vnode, elm, data, renderer)
8101
8150
  : true;
8102
8151
  const hasMatchingStyleAttr = shouldValidateAttr(data, 'style')
8103
- ? validateStyleAttr(vnode, elm, data, renderer)
8152
+ ? validateStyleAttr(elm, data, renderer)
8104
8153
  : true;
8105
8154
  if (isFalse(hasMatchingAttrs && hasMatchingClass && hasMatchingStyleAttr)) {
8106
8155
  return false;
@@ -8108,10 +8157,10 @@ function haveCompatibleStaticParts(vnode, renderer) {
8108
8157
  }
8109
8158
  else {
8110
8159
  // VStaticPartText
8111
- if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
8160
+ if (!isTypeText(elm)) {
8112
8161
  return false;
8113
8162
  }
8114
- updateTextContent(elm, part, owner, renderer);
8163
+ updateTextContent(elm, part, renderer);
8115
8164
  }
8116
8165
  }
8117
8166
  return true;
@@ -8377,5 +8426,5 @@ function readonly(obj) {
8377
8426
  }
8378
8427
 
8379
8428
  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 */
8429
+ /** version: 8.12.5 */
8381
8430
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export declare const ariaReflectionPolyfillDescriptors: any;
@@ -0,0 +1,2 @@
1
+ declare const descriptors: Record<string, PropertyDescriptor>;
2
+ export { descriptors as propToAttrReflectionPolyfillDescriptors };
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.6-alpha.0",
8
8
  "description": "Core LWC engine APIs.",
9
9
  "keywords": [
10
10
  "lwc"
@@ -46,11 +46,11 @@
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.6-alpha.0",
50
+ "@lwc/shared": "8.12.6-alpha.0",
51
+ "@lwc/signals": "8.12.6-alpha.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "observable-membrane": "2.0.0"
55
55
  }
56
- }
56
+ }