@lwc/engine-core 6.3.4 → 6.4.1

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/dist/index.cjs.js CHANGED
@@ -3649,6 +3649,12 @@ function isVScopedSlotFragment(vnode) {
3649
3649
  function isVStatic(vnode) {
3650
3650
  return vnode.type === 4 /* VNodeType.Static */;
3651
3651
  }
3652
+ function isVStaticPartElement(vnode) {
3653
+ return vnode.type === 1 /* VStaticPartType.Element */;
3654
+ }
3655
+ function isVStaticPartText(vnode) {
3656
+ return vnode.type === 0 /* VStaticPartType.Text */;
3657
+ }
3652
3658
 
3653
3659
  /*
3654
3660
  * Copyright (c) 2018, salesforce.com, inc.
@@ -3658,7 +3664,8 @@ function isVStatic(vnode) {
3658
3664
  */
3659
3665
  const ColonCharCode = 58;
3660
3666
  function patchAttributes(oldVnode, vnode, renderer) {
3661
- const { attrs, external } = vnode.data;
3667
+ const { data, elm } = vnode;
3668
+ const { attrs } = data;
3662
3669
  if (shared.isUndefined(attrs)) {
3663
3670
  return;
3664
3671
  }
@@ -3667,7 +3674,8 @@ function patchAttributes(oldVnode, vnode, renderer) {
3667
3674
  if (oldAttrs === attrs) {
3668
3675
  return;
3669
3676
  }
3670
- const { elm } = vnode;
3677
+ // Note VStaticPartData does not contain the external property so it will always default to false.
3678
+ const external = 'external' in data ? data.external : false;
3671
3679
  const { setAttribute, removeAttribute, setProperty } = renderer;
3672
3680
  for (const key in attrs) {
3673
3681
  const cur = attrs[key];
@@ -3771,8 +3779,7 @@ function patchProps(oldVnode, vnode, renderer) {
3771
3779
  */
3772
3780
  const classNameToClassMap = shared.create(null);
3773
3781
  function getMapFromClassName(className) {
3774
- // Intentionally using == to match undefined and null values from computed style attribute
3775
- if (className == null) {
3782
+ if (shared.isUndefined(className) || shared.isNull(className) || className === '') {
3776
3783
  return EmptyObject;
3777
3784
  }
3778
3785
  // computed class names must be string
@@ -3811,10 +3818,16 @@ function patchClassAttribute(oldVnode, vnode, renderer) {
3811
3818
  if (oldClass === newClass) {
3812
3819
  return;
3813
3820
  }
3814
- const { getClassList } = renderer;
3815
- const classList = getClassList(elm);
3816
3821
  const newClassMap = getMapFromClassName(newClass);
3817
3822
  const oldClassMap = getMapFromClassName(oldClass);
3823
+ if (oldClassMap === newClassMap) {
3824
+ // These objects are cached by className string (`classNameToClassMap`), so we can only get here if there is
3825
+ // a key collision due to types, e.g. oldClass is `undefined` and newClass is `""` (empty string), or oldClass
3826
+ // is `1` (number) and newClass is `"1"` (string).
3827
+ return;
3828
+ }
3829
+ const { getClassList } = renderer;
3830
+ const classList = getClassList(elm);
3818
3831
  let name;
3819
3832
  for (name in oldClassMap) {
3820
3833
  // remove only if it is not in the new class collection and it is not set from within the instance
@@ -3836,8 +3849,13 @@ function patchClassAttribute(oldVnode, vnode, renderer) {
3836
3849
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3837
3850
  */
3838
3851
  // The style property is a string when defined via an expression in the template.
3839
- function patchStyleAttribute(oldVnode, vnode, renderer) {
3852
+ function patchStyleAttribute(oldVnode, vnode, renderer, owner) {
3840
3853
  const { elm, data: { style: newStyle }, } = vnode;
3854
+ if (process.env.NODE_ENV !== 'production') {
3855
+ if (!shared.isNull(newStyle) && !shared.isUndefined(newStyle) && !shared.isString(newStyle)) {
3856
+ logError(`Invalid 'style' attribute passed to <${elm.tagName.toLowerCase()}> is ignored. This attribute must be a string value.`, owner);
3857
+ }
3858
+ }
3841
3859
  const oldStyle = shared.isNull(oldVnode) ? undefined : oldVnode.data.style;
3842
3860
  if (oldStyle === newStyle) {
3843
3861
  return;
@@ -3858,8 +3876,8 @@ function patchStyleAttribute(oldVnode, vnode, renderer) {
3858
3876
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3859
3877
  */
3860
3878
  function applyEventListeners(vnode, renderer) {
3861
- const { elm } = vnode;
3862
- const on = vnode.data?.on;
3879
+ const { elm, data } = vnode;
3880
+ const { on } = data;
3863
3881
  if (shared.isUndefined(on)) {
3864
3882
  return;
3865
3883
  }
@@ -3936,12 +3954,47 @@ function applyRefs(vnode, owner) {
3936
3954
  refVNodes[ref] = vnode;
3937
3955
  }
3938
3956
 
3957
+ /*
3958
+ * Copyright (c) 2024, salesforce.com, inc.
3959
+ * All rights reserved.
3960
+ * SPDX-License-Identifier: MIT
3961
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3962
+ */
3963
+ function patchTextVNode(n1, n2, renderer) {
3964
+ n2.elm = n1.elm;
3965
+ if (n2.text !== n1.text) {
3966
+ updateTextContent$1(n2, renderer);
3967
+ }
3968
+ }
3969
+ function patchTextVStaticPart(n1, n2, renderer) {
3970
+ if (shared.isNull(n1) || n2.text !== n1.text) {
3971
+ updateTextContent$1(n2, renderer);
3972
+ }
3973
+ }
3974
+ function updateTextContent$1(vnode, renderer) {
3975
+ const { elm, text } = vnode;
3976
+ const { setText } = renderer;
3977
+ if (process.env.NODE_ENV !== 'production') {
3978
+ unlockDomMutation();
3979
+ }
3980
+ setText(elm, text);
3981
+ if (process.env.NODE_ENV !== 'production') {
3982
+ lockDomMutation();
3983
+ }
3984
+ }
3985
+
3939
3986
  /*
3940
3987
  * Copyright (c) 2023, salesforce.com, inc.
3941
3988
  * All rights reserved.
3942
3989
  * SPDX-License-Identifier: MIT
3943
3990
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3944
3991
  */
3992
+ /**
3993
+ * Given an array of static parts, mounts the DOM element to the part based on the staticPartId
3994
+ * @param root the root element
3995
+ * @param parts an array of VStaticParts
3996
+ * @param renderer the renderer to use
3997
+ */
3945
3998
  function traverseAndSetElements(root, parts, renderer) {
3946
3999
  const numParts = parts.length;
3947
4000
  // Optimization given that, in most cases, there will be one part, and it's just the root
@@ -4007,18 +4060,30 @@ function mountStaticParts(root, vnode, renderer) {
4007
4060
  traverseAndSetElements(root, parts, renderer);
4008
4061
  // Currently only event listeners and refs are supported for static vnodes
4009
4062
  for (const part of parts) {
4010
- // Event listeners only need to be applied once when mounting
4011
- applyEventListeners(part, renderer);
4012
- // Refs must be updated after every render due to refVNodes getting reset before every render
4013
- applyRefs(part, owner);
4063
+ if (isVStaticPartElement(part)) {
4064
+ // Event listeners only need to be applied once when mounting
4065
+ applyEventListeners(part, renderer);
4066
+ // Refs must be updated after every render due to refVNodes getting reset before every render
4067
+ applyRefs(part, owner);
4068
+ patchAttributes(null, part, renderer);
4069
+ patchClassAttribute(null, part, renderer);
4070
+ patchStyleAttribute(null, part, renderer, owner);
4071
+ }
4072
+ else {
4073
+ if (process.env.NODE_ENV !== 'production' && !isVStaticPartText(part)) {
4074
+ throw new Error(`LWC internal error, encountered unknown static part type: ${part.type}`);
4075
+ }
4076
+ patchTextVStaticPart(null, part, renderer);
4077
+ }
4014
4078
  }
4015
4079
  }
4016
4080
  /**
4017
- * Mounts elements to the newly generated VStatic node
4081
+ * Updates the static elements based on the content of the VStaticParts
4018
4082
  * @param n1 the previous VStatic vnode
4019
4083
  * @param n2 the current VStatic vnode
4084
+ * @param renderer the renderer to use
4020
4085
  */
4021
- function patchStaticParts(n1, n2) {
4086
+ function patchStaticParts(n1, n2, renderer) {
4022
4087
  // On the server, we don't support ref (because it relies on renderedCallback), nor do we
4023
4088
  // support event listeners (no interactivity), so traversing parts makes no sense
4024
4089
  if (!process.env.IS_BROWSER) {
@@ -4033,12 +4098,49 @@ function patchStaticParts(n1, n2) {
4033
4098
  shared.assert.isTrue(currParts.length === prevParts?.length, 'Expected static parts to be the same for the same element. This is an error with the LWC framework itself.');
4034
4099
  }
4035
4100
  for (let i = 0; i < currParts.length; i++) {
4101
+ const prevPart = prevParts[i];
4036
4102
  const part = currParts[i];
4037
4103
  // Patch only occurs if the vnode is newly generated, which means the part.elm is always undefined
4038
4104
  // Since the vnode and elements are the same we can safely assume that prevParts[i].elm is defined.
4039
- part.elm = prevParts[i].elm;
4040
- // Refs must be updated after every render due to refVNodes getting reset before every render
4041
- applyRefs(part, currPartsOwner);
4105
+ part.elm = prevPart.elm;
4106
+ if (process.env.NODE_ENV !== 'production' && prevPart.type !== part.type) {
4107
+ throw new Error(`LWC internal error, static part types do not match. Previous type was ${prevPart.type} and current type is ${part.type}`);
4108
+ }
4109
+ if (isVStaticPartElement(part)) {
4110
+ // Refs must be updated after every render due to refVNodes getting reset before every render
4111
+ applyRefs(part, currPartsOwner);
4112
+ patchAttributes(prevPart, part, renderer);
4113
+ patchClassAttribute(prevPart, part, renderer);
4114
+ patchStyleAttribute(prevPart, part, renderer, currPartsOwner);
4115
+ }
4116
+ else {
4117
+ patchTextVStaticPart(null, part, renderer);
4118
+ }
4119
+ }
4120
+ }
4121
+ /**
4122
+ * Mounts the hydration specific attributes
4123
+ * @param vnode the parent VStatic node
4124
+ * @param renderer the renderer to use
4125
+ */
4126
+ function hydrateStaticParts(vnode, renderer) {
4127
+ if (!process.env.IS_BROWSER) {
4128
+ return;
4129
+ }
4130
+ const { parts, owner } = vnode;
4131
+ if (shared.isUndefined(parts)) {
4132
+ return;
4133
+ }
4134
+ // Note, hydration doesn't patch attributes because hydration validation occurs before this routine
4135
+ // which guarantees that the elements are the same.
4136
+ // We only need to apply the parts for things that cannot be done on the server.
4137
+ for (const part of parts) {
4138
+ if (isVStaticPartElement(part)) {
4139
+ // Event listeners only need to be applied once when mounting
4140
+ applyEventListeners(part, renderer);
4141
+ // Refs must be updated after every render due to refVNodes getting reset before every render
4142
+ applyRefs(part, owner);
4143
+ }
4042
4144
  }
4043
4145
  }
4044
4146
 
@@ -4076,7 +4178,7 @@ function patch(n1, n2, parent, renderer) {
4076
4178
  switch (n2.type) {
4077
4179
  case 0 /* VNodeType.Text */:
4078
4180
  // VText has no special capability, fallback to the owner's renderer
4079
- patchText(n1, n2, renderer);
4181
+ patchTextVNode(n1, n2, renderer);
4080
4182
  break;
4081
4183
  case 1 /* VNodeType.Comment */:
4082
4184
  // VComment has no special capability, fallback to the owner's renderer
@@ -4123,12 +4225,6 @@ function mount(node, parent, renderer, anchor) {
4123
4225
  break;
4124
4226
  }
4125
4227
  }
4126
- function patchText(n1, n2, renderer) {
4127
- n2.elm = n1.elm;
4128
- if (n2.text !== n1.text) {
4129
- updateTextContent(n2, renderer);
4130
- }
4131
- }
4132
4228
  function mountText(vnode, parent, anchor, renderer) {
4133
4229
  const { owner } = vnode;
4134
4230
  const { createText } = renderer;
@@ -4141,7 +4237,7 @@ function patchComment(n1, n2, renderer) {
4141
4237
  // FIXME: Comment nodes should be static, we shouldn't need to diff them together. However
4142
4238
  // it is the case today.
4143
4239
  if (n2.text !== n1.text) {
4144
- updateTextContent(n2, renderer);
4240
+ updateTextContent$1(n2, renderer);
4145
4241
  }
4146
4242
  }
4147
4243
  function mountComment(vnode, parent, anchor, renderer) {
@@ -4185,7 +4281,7 @@ function patchStatic(n1, n2, renderer) {
4185
4281
  // slotAssignments can only apply to the top level element, never to a static part.
4186
4282
  patchSlotAssignment(n1, n2, renderer);
4187
4283
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
4188
- patchStaticParts(n1, n2);
4284
+ patchStaticParts(n1, n2, renderer);
4189
4285
  }
4190
4286
  function patchElement(n1, n2, renderer) {
4191
4287
  const elm = (n2.elm = n1.elm);
@@ -4196,19 +4292,20 @@ function mountStatic(vnode, parent, anchor, renderer) {
4196
4292
  const { owner } = vnode;
4197
4293
  const { cloneNode, isSyntheticShadowDefined } = renderer;
4198
4294
  const elm = (vnode.elm = cloneNode(vnode.fragment, true));
4295
+ // Define the root node shadow resolver
4199
4296
  linkNodeToShadow(elm, owner, renderer);
4200
4297
  applyElementRestrictions(elm, vnode);
4201
- // Marks this node as Static to propagate the shadow resolver. must happen after elm is assigned to the proper shadow
4202
4298
  const { renderMode, shadowMode } = owner;
4203
4299
  if (isSyntheticShadowDefined) {
4300
+ // Marks this node as Static to propagate the shadow resolver. must happen after elm is assigned to the proper shadow
4204
4301
  if (shadowMode === 1 /* ShadowMode.Synthetic */ || renderMode === 0 /* RenderMode.Light */) {
4205
4302
  elm[shared.KEY__SHADOW_STATIC] = true;
4206
4303
  }
4207
4304
  }
4208
4305
  // slotAssignments can only apply to the top level element, never to a static part.
4209
4306
  patchSlotAssignment(null, vnode, renderer);
4210
- insertNode(elm, parent, anchor, renderer);
4211
4307
  mountStaticParts(elm, vnode, renderer);
4308
+ insertNode(elm, parent, anchor, renderer);
4212
4309
  }
4213
4310
  function mountCustomElement(vnode, parent, anchor, renderer) {
4214
4311
  const { sel, owner, ctor } = vnode;
@@ -4373,17 +4470,6 @@ function linkNodeToShadow(elm, owner, renderer) {
4373
4470
  }
4374
4471
  }
4375
4472
  }
4376
- function updateTextContent(vnode, renderer) {
4377
- const { elm, text } = vnode;
4378
- const { setText } = renderer;
4379
- if (process.env.NODE_ENV !== 'production') {
4380
- unlockDomMutation();
4381
- }
4382
- setText(elm, text);
4383
- if (process.env.NODE_ENV !== 'production') {
4384
- lockDomMutation();
4385
- }
4386
- }
4387
4473
  function insertFragmentOrNode(vnode, parent, anchor, renderer) {
4388
4474
  if (process.env.NODE_ENV !== 'production') {
4389
4475
  unlockDomMutation();
@@ -4428,15 +4514,16 @@ function patchElementPropsAndAttrsAndRefs$1(oldVnode, vnode, renderer) {
4428
4514
  applyStaticClassAttribute(vnode, renderer);
4429
4515
  applyStaticStyleAttribute(vnode, renderer);
4430
4516
  }
4517
+ const { owner } = vnode;
4431
4518
  // Attrs need to be applied to element before props IE11 will wipe out value on radio inputs if
4432
4519
  // value is set before type=radio.
4433
4520
  patchClassAttribute(oldVnode, vnode, renderer);
4434
- patchStyleAttribute(oldVnode, vnode, renderer);
4521
+ patchStyleAttribute(oldVnode, vnode, renderer, owner);
4435
4522
  patchAttributes(oldVnode, vnode, renderer);
4436
4523
  patchProps(oldVnode, vnode, renderer);
4437
4524
  patchSlotAssignment(oldVnode, vnode, renderer);
4438
4525
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
4439
- applyRefs(vnode, vnode.owner);
4526
+ applyRefs(vnode, owner);
4440
4527
  }
4441
4528
  function applyStyleScoping(elm, owner, renderer) {
4442
4529
  const { getClassList } = renderer;
@@ -4838,10 +4925,14 @@ function addVNodeToChildLWC(vnode) {
4838
4925
  shared.ArrayPush.call(getVMBeingRendered().velements, vnode);
4839
4926
  }
4840
4927
  // [s]tatic [p]art
4841
- function sp(partId, data) {
4928
+ function sp(partId, data, text) {
4929
+ // Static part will always have either text or data, it's guaranteed by the compiler.
4930
+ const type = shared.isNull(text) ? 1 /* VStaticPartType.Element */ : 0 /* VStaticPartType.Text */;
4842
4931
  return {
4932
+ type,
4843
4933
  partId,
4844
4934
  data,
4935
+ text,
4845
4936
  elm: undefined, // elm is defined later
4846
4937
  };
4847
4938
  }
@@ -4858,8 +4949,9 @@ function ssf(slotName, factory) {
4858
4949
  };
4859
4950
  }
4860
4951
  // [st]atic node
4861
- function st(fragment, key, parts) {
4952
+ function st(fragmentFactory, key, parts) {
4862
4953
  const owner = getVMBeingRendered();
4954
+ const fragment = fragmentFactory(parts);
4863
4955
  const vnode = {
4864
4956
  type: 4 /* VNodeType.Static */,
4865
4957
  sel: undefined,
@@ -4901,9 +4993,6 @@ function h(sel, data, children = EmptyArray) {
4901
4993
  // checking reserved internal data properties
4902
4994
  shared.assert.isFalse(data.className && data.classMap, `vnode.data.className and vnode.data.classMap ambiguous declaration.`);
4903
4995
  shared.assert.isFalse(data.styleDecls && data.style, `vnode.data.styleDecls and vnode.data.style ambiguous declaration.`);
4904
- if (data.style && !shared.isString(data.style)) {
4905
- logError(`Invalid 'style' attribute passed to <${sel}> is ignored. This attribute must be a string value.`, vmBeingRendered);
4906
- }
4907
4996
  shared.forEach.call(children, (childVnode) => {
4908
4997
  if (childVnode != null) {
4909
4998
  shared.assert.isTrue('type' in childVnode &&
@@ -5113,16 +5202,19 @@ function i(iterable, factory) {
5113
5202
  if (process.env.NODE_ENV !== 'production') {
5114
5203
  const vnodes = shared.isArray(vnode) ? vnode : [vnode];
5115
5204
  shared.forEach.call(vnodes, (childVnode) => {
5116
- if (!shared.isNull(childVnode) && shared.isObject(childVnode) && !shared.isUndefined(childVnode.sel)) {
5205
+ // Check that the child vnode is either an element or VStatic
5206
+ if (!shared.isNull(childVnode) && (isVBaseElement(childVnode) || isVStatic(childVnode))) {
5117
5207
  const { key } = childVnode;
5208
+ // In @lwc/engine-server the fragment doesn't have a tagName, default to the VM's tagName.
5209
+ const { tagName } = vmBeingRendered;
5118
5210
  if (shared.isString(key) || shared.isNumber(key)) {
5119
5211
  if (keyMap[key] === 1 && shared.isUndefined(iterationError)) {
5120
- iterationError = `Duplicated "key" attribute value for "<${childVnode.sel}>" in ${vmBeingRendered} for item number ${j}. A key with value "${childVnode.key}" appears more than once in the iteration. Key values must be unique numbers or strings.`;
5212
+ iterationError = `Duplicated "key" attribute value in "<${tagName}>" for item number ${j}. A key with value "${key}" appears more than once in the iteration. Key values must be unique numbers or strings.`;
5121
5213
  }
5122
5214
  keyMap[key] = 1;
5123
5215
  }
5124
5216
  else if (shared.isUndefined(iterationError)) {
5125
- iterationError = `Invalid "key" attribute value in "<${childVnode.sel}>" in ${vmBeingRendered} for item number ${j}. Set a unique "key" value on all iterated child elements.`;
5217
+ iterationError = `Invalid "key" attribute value in "<${tagName}>" for item number ${j}. Set a unique "key" value on all iterated child elements.`;
5126
5218
  }
5127
5219
  }
5128
5220
  });
@@ -5521,6 +5613,111 @@ function validateLightDomTemplate(template, vm) {
5521
5613
  }
5522
5614
  }
5523
5615
  }
5616
+ const browserExpressionSerializer = (partToken, classAttrToken) => {
5617
+ // This will insert the scoped style token as a static class attribute in the fragment
5618
+ // bypassing the need to call applyStyleScoping when mounting static parts.
5619
+ const type = shared.StringCharAt.call(partToken, 0);
5620
+ switch (type) {
5621
+ case "c" /* STATIC_PART_TOKEN_ID.CLASS */:
5622
+ return classAttrToken;
5623
+ case "t" /* STATIC_PART_TOKEN_ID.TEXT */:
5624
+ // Using a single space here gives us a single empty text node
5625
+ return ' ';
5626
+ default:
5627
+ return '';
5628
+ }
5629
+ };
5630
+ const serializerNoop = () => {
5631
+ throw new Error('LWC internal error, attempted to serialize partToken without static parts');
5632
+ };
5633
+ // This function serializes the expressions generated by static content optimization.
5634
+ // Currently this is only needed for SSR.
5635
+ // TODO [#4078]: Split the implementation between @lwc/engine-dom and @lwc/engine-server
5636
+ function buildSerializeExpressionFn(parts) {
5637
+ if (process.env.IS_BROWSER) {
5638
+ return browserExpressionSerializer;
5639
+ }
5640
+ if (shared.isUndefined(parts)) {
5641
+ // Technically this should not be reachable, if there are no parts there should be no partTokens
5642
+ // and this function should never be invoked.
5643
+ return serializerNoop;
5644
+ }
5645
+ const partIdsToParts = new Map();
5646
+ for (const staticPart of parts) {
5647
+ partIdsToParts.set(`${staticPart.partId}`, staticPart);
5648
+ }
5649
+ const parsePartToken = (partToken) => {
5650
+ // The partTokens are split into 3 section:
5651
+ // 1. The first character represents the expression type (attribute, class, style, or text).
5652
+ // 2. For attributes, the characters from index 1 to the first occurrence of a ':' is the partId.
5653
+ // 3. Everything after the first ':' represents the attribute name.
5654
+ // 4. For non-attributes everything from index 1 to the string length is the partId.
5655
+ // Ex, attribute: a0:data-name, a = an attribute, 0 = partId, data-name = attribute name.
5656
+ // Ex, style: s0, s = a style attribute, 0 = partId.
5657
+ const type = shared.StringCharAt.call(partToken, 0);
5658
+ let delimiterIndex = partToken.length;
5659
+ let attrName = '';
5660
+ if (type === "a" /* STATIC_PART_TOKEN_ID.ATTRIBUTE */) {
5661
+ delimiterIndex = partToken.indexOf(':');
5662
+ // Only VStaticPartData.attrs have an attribute name
5663
+ attrName = partToken.substring(delimiterIndex + 1);
5664
+ }
5665
+ const partId = partToken.substring(1, delimiterIndex);
5666
+ const part = partIdsToParts.get(partId) ?? EmptyObject;
5667
+ return { type, part, attrName };
5668
+ };
5669
+ return (partToken, classToken) => {
5670
+ const { type, part, attrName } = parsePartToken(partToken);
5671
+ switch (type) {
5672
+ case "a" /* STATIC_PART_TOKEN_ID.ATTRIBUTE */:
5673
+ return serializeAttribute(part, attrName);
5674
+ case "c" /* STATIC_PART_TOKEN_ID.CLASS */: // class
5675
+ return serializeClassAttribute(part, classToken);
5676
+ case "s" /* STATIC_PART_TOKEN_ID.STYLE */: // style
5677
+ return serializeStyleAttribute(part);
5678
+ case "t" /* STATIC_PART_TOKEN_ID.TEXT */: // text
5679
+ return serializeTextContent(part);
5680
+ default:
5681
+ // This should not be reachable
5682
+ throw new Error(`LWC internal error, unrecognized part token during serialization ${partToken}`);
5683
+ }
5684
+ };
5685
+ }
5686
+ function serializeTextContent(part) {
5687
+ const { text } = part;
5688
+ if (text === '') {
5689
+ return '\u200D'; // Special serialization for empty text nodes
5690
+ }
5691
+ // Note the serialization logic doesn't need to validate against the style tag as in serializeTextContent
5692
+ // because style tags are always inserted through the engine.
5693
+ // User input of style tags are blocked, furthermore, all dynamic text is escaped at this point.
5694
+ return shared.htmlEscape(text);
5695
+ }
5696
+ function serializeStyleAttribute(part) {
5697
+ const { data: { style }, } = part;
5698
+ // This is designed to mirror logic patchStyleAttribute
5699
+ return shared.isString(style) && style.length ? ` style="${shared.htmlEscape(style, true)}"` : '';
5700
+ }
5701
+ function serializeAttribute(part, name) {
5702
+ const { data: { attrs = {} }, } = part;
5703
+ const rawValue = attrs[name];
5704
+ let value = '';
5705
+ // The undefined and null checks here are designed to match patchAttributes routine.
5706
+ if (!shared.isUndefined(rawValue) && !shared.isNull(rawValue)) {
5707
+ const stringifiedValue = String(rawValue);
5708
+ value = stringifiedValue.length
5709
+ ? ` ${name}="${shared.htmlEscape(stringifiedValue, true)}"`
5710
+ : ` ${name}`;
5711
+ }
5712
+ return value;
5713
+ }
5714
+ function serializeClassAttribute(part, classToken) {
5715
+ const classMap = getMapFromClassName(part.data?.className);
5716
+ // Trim the leading and trailing whitespace here because classToken contains a leading space and
5717
+ // there will be a trailing space if classMap is empty.
5718
+ const computedClassName = `${classToken} ${shared.keys(classMap).join(' ')}`.trim();
5719
+ return computedClassName.length ? ` class="${shared.htmlEscape(computedClassName, true)}"` : '';
5720
+ }
5524
5721
  // This should be a no-op outside of LWC's Karma tests, where it's not needed
5525
5722
  let registerFragmentCache = shared.noop;
5526
5723
  // Only used in LWC's Karma tests
@@ -5542,7 +5739,7 @@ function buildParseFragmentFn(createFragmentFn) {
5542
5739
  return (strings, ...keys) => {
5543
5740
  const cache = shared.create(null);
5544
5741
  registerFragmentCache(cache);
5545
- return function () {
5742
+ return function (parts) {
5546
5743
  const { context: { hasScopedStyles, stylesheetToken, legacyStylesheetToken }, shadowMode, renderer, } = getVMBeingRendered();
5547
5744
  const hasStyleToken = !shared.isUndefined(stylesheetToken);
5548
5745
  const isSyntheticShadow = shadowMode === 1 /* ShadowMode.Synthetic */;
@@ -5554,14 +5751,27 @@ function buildParseFragmentFn(createFragmentFn) {
5554
5751
  if (hasStyleToken && isSyntheticShadow) {
5555
5752
  cacheKey |= 2 /* FragmentCache.SHADOW_MODE_SYNTHETIC */;
5556
5753
  }
5557
- if (!shared.isUndefined(cache[cacheKey])) {
5558
- return cache[cacheKey];
5754
+ // Cache is only here to prevent calling innerHTML multiple times which doesn't happen on the server.
5755
+ if (process.env.IS_BROWSER) {
5756
+ // Disable this on the server to prevent cache poisoning when expressions are used.
5757
+ const cached = cache[cacheKey];
5758
+ if (!shared.isUndefined(cached)) {
5759
+ return cached;
5760
+ }
5559
5761
  }
5560
5762
  // If legacy stylesheet tokens are required, then add them to the rendered string
5561
5763
  const stylesheetTokenToRender = stylesheetToken + (hasLegacyToken ? ` ${legacyStylesheetToken}` : '');
5562
5764
  const classToken = hasScopedStyles && hasStyleToken ? ' ' + stylesheetTokenToRender : '';
5563
5765
  const classAttrToken = hasScopedStyles && hasStyleToken ? ` class="${stylesheetTokenToRender}"` : '';
5564
5766
  const attrToken = hasStyleToken && isSyntheticShadow ? ' ' + stylesheetTokenToRender : '';
5767
+ // In the browser, we provide the entire class attribute as a perf optimization to avoid applying it on mount.
5768
+ // The remaining class expression will be applied when the static parts are mounted.
5769
+ // In SSR, the entire class attribute (expression included) is assembled along with the fragment.
5770
+ // This is why in the browser we provide the entire class attribute and in SSR we only provide the class token.
5771
+ const exprClassToken = process.env.IS_BROWSER ? classAttrToken : classToken;
5772
+ // TODO [#3624]: The implementation of this function should be specific to @lwc/engine-dom and @lwc/engine-server.
5773
+ // Find a way to split this in a future refactor.
5774
+ const serializeExpression = buildSerializeExpressionFn(parts);
5565
5775
  let htmlFragment = '';
5566
5776
  for (let i = 0, n = keys.length; i < n; i++) {
5567
5777
  switch (keys[i]) {
@@ -5577,6 +5787,10 @@ function buildParseFragmentFn(createFragmentFn) {
5577
5787
  case 3: // ${1}${2}
5578
5788
  htmlFragment += strings[i] + classAttrToken + attrToken;
5579
5789
  break;
5790
+ default: // expressions ${partId:attributeName/textId}
5791
+ htmlFragment +=
5792
+ strings[i] + serializeExpression(keys[i], exprClassToken);
5793
+ break;
5580
5794
  }
5581
5795
  }
5582
5796
  htmlFragment += strings[strings.length - 1];
@@ -6459,25 +6673,25 @@ function forceRehydration(vm) {
6459
6673
  scheduleRehydration(vm);
6460
6674
  }
6461
6675
  }
6462
- function runFormAssociatedCustomElementCallback(vm, faceCb) {
6676
+ function runFormAssociatedCustomElementCallback(vm, faceCb, args) {
6463
6677
  const { renderMode, shadowMode } = vm;
6464
6678
  if (shadowMode === 1 /* ShadowMode.Synthetic */ && renderMode !== 0 /* RenderMode.Light */) {
6465
6679
  throw new Error('Form associated lifecycle methods are not available in synthetic shadow. Please use native shadow or light DOM.');
6466
6680
  }
6467
- invokeComponentCallback(vm, faceCb);
6681
+ invokeComponentCallback(vm, faceCb, args);
6468
6682
  }
6469
- function runFormAssociatedCallback(elm) {
6683
+ function runFormAssociatedCallback(elm, form) {
6470
6684
  const vm = getAssociatedVM(elm);
6471
6685
  const { formAssociatedCallback } = vm.def;
6472
6686
  if (!shared.isUndefined(formAssociatedCallback)) {
6473
- runFormAssociatedCustomElementCallback(vm, formAssociatedCallback);
6687
+ runFormAssociatedCustomElementCallback(vm, formAssociatedCallback, [form]);
6474
6688
  }
6475
6689
  }
6476
- function runFormDisabledCallback(elm) {
6690
+ function runFormDisabledCallback(elm, disabled) {
6477
6691
  const vm = getAssociatedVM(elm);
6478
6692
  const { formDisabledCallback } = vm.def;
6479
6693
  if (!shared.isUndefined(formDisabledCallback)) {
6480
- runFormAssociatedCustomElementCallback(vm, formDisabledCallback);
6694
+ runFormAssociatedCustomElementCallback(vm, formDisabledCallback, [disabled]);
6481
6695
  }
6482
6696
  }
6483
6697
  function runFormResetCallback(elm) {
@@ -6487,11 +6701,11 @@ function runFormResetCallback(elm) {
6487
6701
  runFormAssociatedCustomElementCallback(vm, formResetCallback);
6488
6702
  }
6489
6703
  }
6490
- function runFormStateRestoreCallback(elm) {
6704
+ function runFormStateRestoreCallback(elm, state, reason) {
6491
6705
  const vm = getAssociatedVM(elm);
6492
6706
  const { formStateRestoreCallback } = vm.def;
6493
6707
  if (!shared.isUndefined(formStateRestoreCallback)) {
6494
- runFormAssociatedCustomElementCallback(vm, formStateRestoreCallback);
6708
+ runFormAssociatedCustomElementCallback(vm, formStateRestoreCallback, [state, reason]);
6495
6709
  }
6496
6710
  }
6497
6711
  function resetRefVNodes(vm) {
@@ -6859,9 +7073,12 @@ function hydrateText(node, vnode, renderer) {
6859
7073
  if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
6860
7074
  return handleMismatch(node, vnode, renderer);
6861
7075
  }
7076
+ return updateTextContent(node, vnode, vnode.owner, renderer);
7077
+ }
7078
+ function updateTextContent(node, vnode, owner, renderer) {
6862
7079
  if (process.env.NODE_ENV !== 'production') {
6863
7080
  if (!textNodeContentsAreEqual(node, vnode, renderer)) {
6864
- logWarn('Hydration mismatch: text values do not match, will recover from the difference', vnode.owner);
7081
+ logWarn('Hydration mismatch: text values do not match, will recover from the difference', owner);
6865
7082
  }
6866
7083
  }
6867
7084
  const { setText } = renderer;
@@ -6887,11 +7104,24 @@ function hydrateComment(node, vnode, renderer) {
6887
7104
  }
6888
7105
  function hydrateStaticElement(elm, vnode, renderer) {
6889
7106
  if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
6890
- !areCompatibleNodes(vnode.fragment, elm, vnode, renderer)) {
7107
+ !areCompatibleStaticNodes(vnode.fragment, elm, vnode, renderer)) {
7108
+ return handleMismatch(elm, vnode, renderer);
7109
+ }
7110
+ return hydrateStaticElementParts(elm, vnode, renderer);
7111
+ }
7112
+ function hydrateStaticElementParts(elm, vnode, renderer) {
7113
+ const { parts } = vnode;
7114
+ if (!shared.isUndefined(parts)) {
7115
+ // Elements must first be set on the static part to validate against.
7116
+ traverseAndSetElements(elm, parts, renderer);
7117
+ }
7118
+ if (!haveCompatibleStaticParts(vnode, renderer)) {
6891
7119
  return handleMismatch(elm, vnode, renderer);
6892
7120
  }
6893
7121
  vnode.elm = elm;
6894
- mountStaticParts(elm, vnode, renderer);
7122
+ // Hydration only requires applying event listeners and refs.
7123
+ // All other expressions should be applied during SSR or through the handleMismatch routine.
7124
+ hydrateStaticParts(vnode, renderer);
6895
7125
  return elm;
6896
7126
  }
6897
7127
  function hydrateFragment(elm, vnode, renderer) {
@@ -7067,12 +7297,13 @@ function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true
7067
7297
  }
7068
7298
  return false;
7069
7299
  }
7070
- const hasCompatibleAttrs = validateAttrs(vnode, elm, renderer, shouldValidateAttr);
7300
+ const { data } = vnode;
7301
+ const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7071
7302
  const hasCompatibleClass = shouldValidateAttr('class')
7072
- ? validateClassAttr(vnode, elm, renderer)
7303
+ ? validateClassAttr(vnode, elm, data, renderer)
7073
7304
  : true;
7074
7305
  const hasCompatibleStyle = shouldValidateAttr('style')
7075
- ? validateStyleAttr(vnode, elm, renderer)
7306
+ ? validateStyleAttr(vnode, elm, data, renderer)
7076
7307
  : true;
7077
7308
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7078
7309
  }
@@ -7089,8 +7320,8 @@ function attributeValuesAreEqual(vnodeValue, value) {
7089
7320
  // In all other cases, the two values are not considered equal
7090
7321
  return false;
7091
7322
  }
7092
- function validateAttrs(vnode, elm, renderer, shouldValidateAttr) {
7093
- const { data: { attrs = {} }, } = vnode;
7323
+ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7324
+ const { attrs = {} } = data;
7094
7325
  let nodesAreCompatible = true;
7095
7326
  // Validate attributes, though we could always recovery from those by running the update mods.
7096
7327
  // Note: intentionally ONLY matching vnodes.attrs to elm.attrs, in case SSR is adding extra attributes.
@@ -7098,21 +7329,22 @@ function validateAttrs(vnode, elm, renderer, shouldValidateAttr) {
7098
7329
  if (!shouldValidateAttr(attrName)) {
7099
7330
  continue;
7100
7331
  }
7101
- const { owner } = vnode;
7102
7332
  const { getAttribute } = renderer;
7103
7333
  const elmAttrValue = getAttribute(elm, attrName);
7104
7334
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7105
7335
  if (process.env.NODE_ENV !== 'production') {
7106
7336
  const { getProperty } = renderer;
7107
- logError(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found ${shared.isNull(elmAttrValue) ? 'null' : `"${elmAttrValue}"`}`, owner);
7337
+ logError(`Mismatch hydrating element <${getProperty(elm, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found ${shared.isNull(elmAttrValue) ? 'null' : `"${elmAttrValue}"`}`, vnode.owner);
7108
7338
  }
7109
7339
  nodesAreCompatible = false;
7110
7340
  }
7111
7341
  }
7112
7342
  return nodesAreCompatible;
7113
7343
  }
7114
- function validateClassAttr(vnode, elm, renderer) {
7115
- const { data, owner } = vnode;
7344
+ function validateClassAttr(vnode, elm, data, renderer) {
7345
+ const { owner } = vnode;
7346
+ // classMap is never available on VStaticPartData so it can default to undefined
7347
+ // casting to prevent TS error.
7116
7348
  let { className, classMap } = data;
7117
7349
  const { getProperty, getClassList, getAttribute } = renderer;
7118
7350
  // we don't care about legacy for hydration. it's a new use case
@@ -7185,8 +7417,9 @@ function validateClassAttr(vnode, elm, renderer) {
7185
7417
  }
7186
7418
  return nodesAreCompatible;
7187
7419
  }
7188
- function validateStyleAttr(vnode, elm, renderer) {
7189
- const { data: { style, styleDecls }, } = vnode;
7420
+ function validateStyleAttr(vnode, elm, data, renderer) {
7421
+ // Note styleDecls is always undefined for VStaticPartData, casting here to default it to undefined
7422
+ const { style, styleDecls } = data;
7190
7423
  const { getAttribute } = renderer;
7191
7424
  const elmStyle = getAttribute(elm, 'style') || '';
7192
7425
  let vnodeStyle;
@@ -7226,7 +7459,7 @@ function validateStyleAttr(vnode, elm, renderer) {
7226
7459
  }
7227
7460
  return nodesAreCompatible;
7228
7461
  }
7229
- function areCompatibleNodes(client, ssr, vnode, renderer) {
7462
+ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
7230
7463
  const { getProperty, getAttribute } = renderer;
7231
7464
  if (getProperty(client, 'nodeType') === 3 /* EnvNodeTypes.TEXT */) {
7232
7465
  if (!hasCorrectNodeType(vnode, ssr, 3 /* EnvNodeTypes.TEXT */, renderer)) {
@@ -7243,24 +7476,63 @@ function areCompatibleNodes(client, ssr, vnode, renderer) {
7243
7476
  if (!hasCorrectNodeType(vnode, ssr, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
7244
7477
  return false;
7245
7478
  }
7479
+ const { owner, parts } = vnode;
7246
7480
  let isCompatibleElements = true;
7247
7481
  if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
7248
7482
  if (process.env.NODE_ENV !== 'production') {
7249
- logError(`Hydration mismatch: expecting element with tag "${getProperty(client, 'tagName').toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, vnode.owner);
7483
+ logError(`Hydration mismatch: expecting element with tag "${getProperty(client, 'tagName').toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, owner);
7250
7484
  }
7251
7485
  return false;
7252
7486
  }
7253
7487
  const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
7254
7488
  clientAttrsNames.forEach((attrName) => {
7255
7489
  if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
7256
- if (process.env.NODE_ENV !== 'production') {
7257
- logError(`Mismatch hydrating element <${getProperty(client, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${getAttribute(client, attrName)}" but found "${getAttribute(ssr, attrName)}"`, vnode.owner);
7490
+ // Check if the root element attributes have expressions, if it does then we need to delegate hydration
7491
+ // validation to haveCompatibleStaticParts.
7492
+ // Note if there are no parts then it is a fully static fragment.
7493
+ // partId === 0 will always refer to the root element, this is guaranteed by the compiler.
7494
+ if (parts?.[0].partId !== 0) {
7495
+ if (process.env.NODE_ENV !== 'production') {
7496
+ logError(`Mismatch hydrating element <${getProperty(client, 'tagName').toLowerCase()}>: attribute "${attrName}" has different values, expected "${getAttribute(client, attrName)}" but found "${getAttribute(ssr, attrName)}"`, owner);
7497
+ }
7498
+ isCompatibleElements = false;
7258
7499
  }
7259
- isCompatibleElements = false;
7260
7500
  }
7261
7501
  });
7262
7502
  return isCompatibleElements;
7263
7503
  }
7504
+ function haveCompatibleStaticParts(vnode, renderer) {
7505
+ const { parts, owner } = vnode;
7506
+ if (shared.isUndefined(parts)) {
7507
+ return true;
7508
+ }
7509
+ // The validation here relies on 2 key invariants:
7510
+ // 1. It's never the case that `parts` is undefined on the server but defined on the client (or vice-versa)
7511
+ // 2. It's never the case that `parts` has one length on the server but another on the client
7512
+ for (const part of parts) {
7513
+ const { elm } = part;
7514
+ if (isVStaticPartElement(part)) {
7515
+ if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
7516
+ return false;
7517
+ }
7518
+ const { data } = part;
7519
+ const hasMatchingAttrs = validateAttrs(vnode, elm, data, renderer, () => true);
7520
+ const hasMatchingStyleAttr = validateStyleAttr(vnode, elm, data, renderer);
7521
+ const hasMatchingClass = validateClassAttr(vnode, elm, data, renderer);
7522
+ if (shared.isFalse(hasMatchingAttrs && hasMatchingStyleAttr && hasMatchingClass)) {
7523
+ return false;
7524
+ }
7525
+ }
7526
+ else {
7527
+ // VStaticPartText
7528
+ if (!hasCorrectNodeType(vnode, elm, 3 /* EnvNodeTypes.TEXT */, renderer)) {
7529
+ return false;
7530
+ }
7531
+ updateTextContent(elm, part, owner, renderer);
7532
+ }
7533
+ }
7534
+ return true;
7535
+ }
7264
7536
 
7265
7537
  /*
7266
7538
  * Copyright (c) 2018, salesforce.com, inc.
@@ -7575,5 +7847,5 @@ exports.swapTemplate = swapTemplate;
7575
7847
  exports.track = track;
7576
7848
  exports.unwrap = unwrap;
7577
7849
  exports.wire = wire;
7578
- /** version: 6.3.4 */
7850
+ /** version: 6.4.1 */
7579
7851
  //# sourceMappingURL=index.cjs.js.map