@lwc/engine-core 2.37.2 → 2.37.3

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.
@@ -6231,15 +6231,44 @@ function hydrateNode(node, vnode, renderer) {
6231
6231
  return renderer.nextSibling(hydratedNode);
6232
6232
  }
6233
6233
  const NODE_VALUE_PROP = 'nodeValue';
6234
+ const PARENT_NODE_PROP = 'parentNode';
6235
+ const TAG_NAME_PROP = 'tagName';
6236
+ function textNodeContentsAreEqual(node, vnode, renderer) {
6237
+ const { getProperty } = renderer;
6238
+ const nodeValue = getProperty(node, NODE_VALUE_PROP);
6239
+ if (nodeValue === vnode.text) {
6240
+ return true;
6241
+ }
6242
+ // Special case for empty text nodes – these are serialized differently on the server
6243
+ // See https://github.com/salesforce/lwc/pull/2656
6244
+ if (nodeValue === '\u200D' && vnode.text === '') {
6245
+ return true;
6246
+ }
6247
+ // Special case for text nodes inside `<style>` tags – these are escaped when rendered server-size,
6248
+ // but not when generated by the engine client-side.
6249
+ const parentNode = getProperty(node, PARENT_NODE_PROP);
6250
+ // Should never be null, but just to be safe, we check.
6251
+ /* istanbul ignore else */
6252
+ if (!shared.isNull(parentNode)) {
6253
+ const tagName = getProperty(parentNode, TAG_NAME_PROP);
6254
+ // If the tagName is STYLE, then the following condition should always be true.
6255
+ // The LWC compiler blocks using `<style>`s inside of templates, so it should be impossible
6256
+ // for component authors to render different `<style>` text content on the client and server.
6257
+ // But just to be safe, we check.
6258
+ /* istanbul ignore next */
6259
+ if (tagName === 'STYLE' && shared.htmlEscape(vnode.text) === nodeValue) {
6260
+ return true;
6261
+ }
6262
+ }
6263
+ return false;
6264
+ }
6234
6265
  function hydrateText(node, vnode, renderer) {
6235
6266
  var _a;
6236
6267
  if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
6237
6268
  return handleMismatch(node, vnode, renderer);
6238
6269
  }
6239
6270
  if (process.env.NODE_ENV !== 'production') {
6240
- const { getProperty } = renderer;
6241
- const nodeValue = getProperty(node, NODE_VALUE_PROP);
6242
- if (nodeValue !== vnode.text && !(nodeValue === '\u200D' && vnode.text === '')) {
6271
+ if (!textNodeContentsAreEqual(node, vnode, renderer)) {
6243
6272
  logWarn('Hydration mismatch: text values do not match, will recover from the difference', vnode.owner);
6244
6273
  }
6245
6274
  }
@@ -6419,6 +6448,19 @@ function isMatchingElement(vnode, elm, renderer) {
6419
6448
  const hasIncompatibleStyle = validateStyleAttr(vnode, elm, renderer);
6420
6449
  return hasIncompatibleAttrs && hasIncompatibleClass && hasIncompatibleStyle;
6421
6450
  }
6451
+ function attributeValuesAreEqual(vnodeValue, value) {
6452
+ const vnodeValueAsString = String(vnodeValue);
6453
+ if (vnodeValueAsString === value) {
6454
+ return true;
6455
+ }
6456
+ // If the expected value is null, this means that the attribute does not exist. In that case,
6457
+ // we accept any nullish value (undefined or null).
6458
+ if (shared.isNull(value) && (shared.isUndefined(vnodeValue) || shared.isNull(vnodeValue))) {
6459
+ return true;
6460
+ }
6461
+ // In all other cases, the two values are not considered equal
6462
+ return false;
6463
+ }
6422
6464
  function validateAttrs(vnode, elm, renderer) {
6423
6465
  const { data: { attrs = {} }, } = vnode;
6424
6466
  let nodesAreCompatible = true;
@@ -6428,10 +6470,10 @@ function validateAttrs(vnode, elm, renderer) {
6428
6470
  const { owner } = vnode;
6429
6471
  const { getAttribute } = renderer;
6430
6472
  const elmAttrValue = getAttribute(elm, attrName);
6431
- if (String(attrValue) !== elmAttrValue) {
6473
+ if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
6432
6474
  if (process.env.NODE_ENV !== 'production') {
6433
6475
  const { getProperty } = renderer;
6434
- logError(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found "${elmAttrValue}"`, owner);
6476
+ logError(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found ${shared.isNull(elmAttrValue) ? 'null' : `"${elmAttrValue}"`}`, owner);
6435
6477
  }
6436
6478
  nodesAreCompatible = false;
6437
6479
  }
@@ -6864,4 +6906,4 @@ exports.swapTemplate = swapTemplate;
6864
6906
  exports.track = track;
6865
6907
  exports.unwrap = unwrap;
6866
6908
  exports.wire = wire;
6867
- /* version: 2.37.2 */
6909
+ /* version: 2.37.3 */
@@ -1,5 +1,5 @@
1
1
  /* proxy-compat-disable */
2
- import { noop, StringToLowerCase, isNull, ArrayPush as ArrayPush$1, ArrayJoin, isFrozen, isUndefined as isUndefined$1, defineProperty, ArrayIndexOf, ArraySplice, create, seal, isArray as isArray$1, isFunction as isFunction$1, keys, hasOwnProperty as hasOwnProperty$1, forEach, AriaPropNameToAttrNameMap, getPropertyDescriptor, defineProperties, getOwnPropertyNames as getOwnPropertyNames$1, getPrototypeOf as getPrototypeOf$1, setPrototypeOf, assign, isObject, assert, freeze, KEY__SYNTHETIC_MODE, toString as toString$1, getOwnPropertyDescriptor as getOwnPropertyDescriptor$1, isFalse, LWC_VERSION_COMMENT_REGEX, LWC_VERSION, htmlPropertyToAttribute, ArraySlice, ArrayMap, KEY__SCOPED_CSS, StringCharCodeAt, XML_NAMESPACE, XLINK_NAMESPACE, htmlAttributeToProperty, isString, StringSlice, isTrue, SVG_NAMESPACE, KEY__SHADOW_STATIC, KEY__SHADOW_RESOLVER, ArraySome, ArrayPop, isNumber, StringReplace, ArrayUnshift, globalThis as globalThis$1, KEY__NATIVE_GET_ELEMENT_BY_ID, KEY__NATIVE_QUERY_SELECTOR_ALL, ID_REFERENCING_ATTRIBUTES_SET, KEY__SHADOW_TOKEN, ArrayFilter, StringSplit, ArrayCopyWithin, ArrayFill, ArraySort, ArrayReverse, ArrayShift } from '@lwc/shared';
2
+ import { noop, StringToLowerCase, isNull, ArrayPush as ArrayPush$1, ArrayJoin, isFrozen, isUndefined as isUndefined$1, defineProperty, ArrayIndexOf, ArraySplice, create, seal, isArray as isArray$1, isFunction as isFunction$1, keys, hasOwnProperty as hasOwnProperty$1, forEach, AriaPropNameToAttrNameMap, getPropertyDescriptor, defineProperties, getOwnPropertyNames as getOwnPropertyNames$1, getPrototypeOf as getPrototypeOf$1, setPrototypeOf, assign, isObject, assert, freeze, KEY__SYNTHETIC_MODE, toString as toString$1, getOwnPropertyDescriptor as getOwnPropertyDescriptor$1, isFalse, LWC_VERSION_COMMENT_REGEX, LWC_VERSION, htmlPropertyToAttribute, ArraySlice, ArrayMap, KEY__SCOPED_CSS, StringCharCodeAt, XML_NAMESPACE, XLINK_NAMESPACE, htmlAttributeToProperty, isString, StringSlice, isTrue, SVG_NAMESPACE, KEY__SHADOW_STATIC, KEY__SHADOW_RESOLVER, ArraySome, ArrayPop, isNumber, StringReplace, ArrayUnshift, globalThis as globalThis$1, KEY__NATIVE_GET_ELEMENT_BY_ID, KEY__NATIVE_QUERY_SELECTOR_ALL, ID_REFERENCING_ATTRIBUTES_SET, KEY__SHADOW_TOKEN, ArrayFilter, StringSplit, htmlEscape, ArrayCopyWithin, ArrayFill, ArraySort, ArrayReverse, ArrayShift } from '@lwc/shared';
3
3
  import { applyAriaReflection } from '@lwc/aria-reflection';
4
4
  export { setFeatureFlag, setFeatureFlagForTest } from '@lwc/features';
5
5
 
@@ -6229,15 +6229,44 @@ function hydrateNode(node, vnode, renderer) {
6229
6229
  return renderer.nextSibling(hydratedNode);
6230
6230
  }
6231
6231
  const NODE_VALUE_PROP = 'nodeValue';
6232
+ const PARENT_NODE_PROP = 'parentNode';
6233
+ const TAG_NAME_PROP = 'tagName';
6234
+ function textNodeContentsAreEqual(node, vnode, renderer) {
6235
+ const { getProperty } = renderer;
6236
+ const nodeValue = getProperty(node, NODE_VALUE_PROP);
6237
+ if (nodeValue === vnode.text) {
6238
+ return true;
6239
+ }
6240
+ // Special case for empty text nodes – these are serialized differently on the server
6241
+ // See https://github.com/salesforce/lwc/pull/2656
6242
+ if (nodeValue === '\u200D' && vnode.text === '') {
6243
+ return true;
6244
+ }
6245
+ // Special case for text nodes inside `<style>` tags – these are escaped when rendered server-size,
6246
+ // but not when generated by the engine client-side.
6247
+ const parentNode = getProperty(node, PARENT_NODE_PROP);
6248
+ // Should never be null, but just to be safe, we check.
6249
+ /* istanbul ignore else */
6250
+ if (!isNull(parentNode)) {
6251
+ const tagName = getProperty(parentNode, TAG_NAME_PROP);
6252
+ // If the tagName is STYLE, then the following condition should always be true.
6253
+ // The LWC compiler blocks using `<style>`s inside of templates, so it should be impossible
6254
+ // for component authors to render different `<style>` text content on the client and server.
6255
+ // But just to be safe, we check.
6256
+ /* istanbul ignore next */
6257
+ if (tagName === 'STYLE' && htmlEscape(vnode.text) === nodeValue) {
6258
+ return true;
6259
+ }
6260
+ }
6261
+ return false;
6262
+ }
6232
6263
  function hydrateText(node, vnode, renderer) {
6233
6264
  var _a;
6234
6265
  if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
6235
6266
  return handleMismatch(node, vnode, renderer);
6236
6267
  }
6237
6268
  if (process.env.NODE_ENV !== 'production') {
6238
- const { getProperty } = renderer;
6239
- const nodeValue = getProperty(node, NODE_VALUE_PROP);
6240
- if (nodeValue !== vnode.text && !(nodeValue === '\u200D' && vnode.text === '')) {
6269
+ if (!textNodeContentsAreEqual(node, vnode, renderer)) {
6241
6270
  logWarn('Hydration mismatch: text values do not match, will recover from the difference', vnode.owner);
6242
6271
  }
6243
6272
  }
@@ -6417,6 +6446,19 @@ function isMatchingElement(vnode, elm, renderer) {
6417
6446
  const hasIncompatibleStyle = validateStyleAttr(vnode, elm, renderer);
6418
6447
  return hasIncompatibleAttrs && hasIncompatibleClass && hasIncompatibleStyle;
6419
6448
  }
6449
+ function attributeValuesAreEqual(vnodeValue, value) {
6450
+ const vnodeValueAsString = String(vnodeValue);
6451
+ if (vnodeValueAsString === value) {
6452
+ return true;
6453
+ }
6454
+ // If the expected value is null, this means that the attribute does not exist. In that case,
6455
+ // we accept any nullish value (undefined or null).
6456
+ if (isNull(value) && (isUndefined$1(vnodeValue) || isNull(vnodeValue))) {
6457
+ return true;
6458
+ }
6459
+ // In all other cases, the two values are not considered equal
6460
+ return false;
6461
+ }
6420
6462
  function validateAttrs(vnode, elm, renderer) {
6421
6463
  const { data: { attrs = {} }, } = vnode;
6422
6464
  let nodesAreCompatible = true;
@@ -6426,10 +6468,10 @@ function validateAttrs(vnode, elm, renderer) {
6426
6468
  const { owner } = vnode;
6427
6469
  const { getAttribute } = renderer;
6428
6470
  const elmAttrValue = getAttribute(elm, attrName);
6429
- if (String(attrValue) !== elmAttrValue) {
6471
+ if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
6430
6472
  if (process.env.NODE_ENV !== 'production') {
6431
6473
  const { getProperty } = renderer;
6432
- logError(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found "${elmAttrValue}"`, owner);
6474
+ logError(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found ${isNull(elmAttrValue) ? 'null' : `"${elmAttrValue}"`}`, owner);
6433
6475
  }
6434
6476
  nodesAreCompatible = false;
6435
6477
  }
@@ -6825,4 +6867,4 @@ function getComponentConstructor(elm) {
6825
6867
  }
6826
6868
 
6827
6869
  export { LightningElement, profilerControl as __unstable__ProfilerControl, reportingControl as __unstable__ReportingControl, api$1 as api, connectRootElement, createContextProvider, createVM, disconnectRootElement, freezeTemplate, getAssociatedVMIfPresent, getComponentConstructor, getComponentDef, getComponentHtmlPrototype, hydrateRoot, isComponentConstructor, parseFragment, parseSVGFragment, readonly, register, registerComponent, registerDecorators, registerTemplate, sanitizeAttribute, setHooks, swapComponent, swapStyle, swapTemplate, track, unwrap, wire };
6828
- /* version: 2.37.2 */
6870
+ /* version: 2.37.3 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lwc/engine-core",
3
- "version": "2.37.2",
3
+ "version": "2.37.3",
4
4
  "description": "Core LWC engine APIs.",
5
5
  "homepage": "https://lwc.dev/",
6
6
  "repository": {
@@ -24,9 +24,9 @@
24
24
  "types/"
25
25
  ],
26
26
  "dependencies": {
27
- "@lwc/aria-reflection": "2.37.2",
28
- "@lwc/features": "2.37.2",
29
- "@lwc/shared": "2.37.2"
27
+ "@lwc/aria-reflection": "2.37.3",
28
+ "@lwc/features": "2.37.3",
29
+ "@lwc/shared": "2.37.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "observable-membrane": "2.0.0"
@@ -68,7 +68,7 @@ export interface VCustomElement extends VBaseElement {
68
68
  }
69
69
  export interface VNodeData {
70
70
  readonly props?: Readonly<Record<string, any>>;
71
- readonly attrs?: Readonly<Record<string, string | number | boolean>>;
71
+ readonly attrs?: Readonly<Record<string, string | number | boolean | null | undefined>>;
72
72
  readonly className?: string;
73
73
  readonly style?: string;
74
74
  readonly classMap?: Readonly<Record<string, boolean>>;