@lwc/engine-core 6.3.3 → 6.4.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.
package/dist/index.cjs.js CHANGED
@@ -503,29 +503,6 @@ function assertNotProd() {
503
503
  throw new ReferenceError();
504
504
  }
505
505
  }
506
- // Temporary fix for when the LWC v5 compiler is used in conjunction with a v6+ engine
507
- // The old compiler format used the "slot" attribute in the `data` bag, whereas the new
508
- // format uses the special `slotAssignment` key.
509
- // This should be removed when the LWC v5 compiler is not used anywhere where it could be mismatched
510
- // with another LWC engine version.
511
- // TODO [#3974]: remove temporary logic to support v5 compiler + v6+ engine
512
- function applyTemporaryCompilerV5SlotFix(data) {
513
- if (lwcRuntimeFlags.DISABLE_TEMPORARY_V5_COMPILER_SUPPORT) {
514
- return data;
515
- }
516
- const { attrs } = data;
517
- if (!shared.isUndefined(attrs)) {
518
- const { slot } = attrs;
519
- if (!shared.isUndefined(slot) && !shared.isNull(slot)) {
520
- return {
521
- ...data,
522
- attrs: cloneAndOmitKey(attrs, 'slot'),
523
- slotAssignment: String(slot),
524
- };
525
- }
526
- }
527
- return data;
528
- }
529
506
  function shouldBeFormAssociated(Ctor) {
530
507
  const ctorFormAssociated = Boolean(Ctor.formAssociated);
531
508
  const apiVersion = getComponentAPIVersion(Ctor);
@@ -3672,6 +3649,12 @@ function isVScopedSlotFragment(vnode) {
3672
3649
  function isVStatic(vnode) {
3673
3650
  return vnode.type === 4 /* VNodeType.Static */;
3674
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
+ }
3675
3658
 
3676
3659
  /*
3677
3660
  * Copyright (c) 2018, salesforce.com, inc.
@@ -3681,7 +3664,8 @@ function isVStatic(vnode) {
3681
3664
  */
3682
3665
  const ColonCharCode = 58;
3683
3666
  function patchAttributes(oldVnode, vnode, renderer) {
3684
- const { attrs, external } = vnode.data;
3667
+ const { data, elm } = vnode;
3668
+ const { attrs } = data;
3685
3669
  if (shared.isUndefined(attrs)) {
3686
3670
  return;
3687
3671
  }
@@ -3690,7 +3674,8 @@ function patchAttributes(oldVnode, vnode, renderer) {
3690
3674
  if (oldAttrs === attrs) {
3691
3675
  return;
3692
3676
  }
3693
- 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;
3694
3679
  const { setAttribute, removeAttribute, setProperty } = renderer;
3695
3680
  for (const key in attrs) {
3696
3681
  const cur = attrs[key];
@@ -3794,8 +3779,7 @@ function patchProps(oldVnode, vnode, renderer) {
3794
3779
  */
3795
3780
  const classNameToClassMap = shared.create(null);
3796
3781
  function getMapFromClassName(className) {
3797
- // Intentionally using == to match undefined and null values from computed style attribute
3798
- if (className == null) {
3782
+ if (shared.isUndefined(className) || shared.isNull(className) || className === '') {
3799
3783
  return EmptyObject;
3800
3784
  }
3801
3785
  // computed class names must be string
@@ -3834,10 +3818,16 @@ function patchClassAttribute(oldVnode, vnode, renderer) {
3834
3818
  if (oldClass === newClass) {
3835
3819
  return;
3836
3820
  }
3837
- const { getClassList } = renderer;
3838
- const classList = getClassList(elm);
3839
3821
  const newClassMap = getMapFromClassName(newClass);
3840
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);
3841
3831
  let name;
3842
3832
  for (name in oldClassMap) {
3843
3833
  // remove only if it is not in the new class collection and it is not set from within the instance
@@ -3859,8 +3849,13 @@ function patchClassAttribute(oldVnode, vnode, renderer) {
3859
3849
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3860
3850
  */
3861
3851
  // The style property is a string when defined via an expression in the template.
3862
- function patchStyleAttribute(oldVnode, vnode, renderer) {
3852
+ function patchStyleAttribute(oldVnode, vnode, renderer, owner) {
3863
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
+ }
3864
3859
  const oldStyle = shared.isNull(oldVnode) ? undefined : oldVnode.data.style;
3865
3860
  if (oldStyle === newStyle) {
3866
3861
  return;
@@ -3881,8 +3876,8 @@ function patchStyleAttribute(oldVnode, vnode, renderer) {
3881
3876
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3882
3877
  */
3883
3878
  function applyEventListeners(vnode, renderer) {
3884
- const { elm } = vnode;
3885
- const on = vnode.data?.on;
3879
+ const { elm, data } = vnode;
3880
+ const { on } = data;
3886
3881
  if (shared.isUndefined(on)) {
3887
3882
  return;
3888
3883
  }
@@ -3959,12 +3954,47 @@ function applyRefs(vnode, owner) {
3959
3954
  refVNodes[ref] = vnode;
3960
3955
  }
3961
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
+
3962
3986
  /*
3963
3987
  * Copyright (c) 2023, salesforce.com, inc.
3964
3988
  * All rights reserved.
3965
3989
  * SPDX-License-Identifier: MIT
3966
3990
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
3967
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
+ */
3968
3998
  function traverseAndSetElements(root, parts, renderer) {
3969
3999
  const numParts = parts.length;
3970
4000
  // Optimization given that, in most cases, there will be one part, and it's just the root
@@ -4030,18 +4060,30 @@ function mountStaticParts(root, vnode, renderer) {
4030
4060
  traverseAndSetElements(root, parts, renderer);
4031
4061
  // Currently only event listeners and refs are supported for static vnodes
4032
4062
  for (const part of parts) {
4033
- // Event listeners only need to be applied once when mounting
4034
- applyEventListeners(part, renderer);
4035
- // Refs must be updated after every render due to refVNodes getting reset before every render
4036
- 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
+ }
4037
4078
  }
4038
4079
  }
4039
4080
  /**
4040
- * Mounts elements to the newly generated VStatic node
4081
+ * Updates the static elements based on the content of the VStaticParts
4041
4082
  * @param n1 the previous VStatic vnode
4042
4083
  * @param n2 the current VStatic vnode
4084
+ * @param renderer the renderer to use
4043
4085
  */
4044
- function patchStaticParts(n1, n2) {
4086
+ function patchStaticParts(n1, n2, renderer) {
4045
4087
  // On the server, we don't support ref (because it relies on renderedCallback), nor do we
4046
4088
  // support event listeners (no interactivity), so traversing parts makes no sense
4047
4089
  if (!process.env.IS_BROWSER) {
@@ -4056,12 +4098,49 @@ function patchStaticParts(n1, n2) {
4056
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.');
4057
4099
  }
4058
4100
  for (let i = 0; i < currParts.length; i++) {
4101
+ const prevPart = prevParts[i];
4059
4102
  const part = currParts[i];
4060
4103
  // Patch only occurs if the vnode is newly generated, which means the part.elm is always undefined
4061
4104
  // Since the vnode and elements are the same we can safely assume that prevParts[i].elm is defined.
4062
- part.elm = prevParts[i].elm;
4063
- // Refs must be updated after every render due to refVNodes getting reset before every render
4064
- 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
+ }
4065
4144
  }
4066
4145
  }
4067
4146
 
@@ -4099,7 +4178,7 @@ function patch(n1, n2, parent, renderer) {
4099
4178
  switch (n2.type) {
4100
4179
  case 0 /* VNodeType.Text */:
4101
4180
  // VText has no special capability, fallback to the owner's renderer
4102
- patchText(n1, n2, renderer);
4181
+ patchTextVNode(n1, n2, renderer);
4103
4182
  break;
4104
4183
  case 1 /* VNodeType.Comment */:
4105
4184
  // VComment has no special capability, fallback to the owner's renderer
@@ -4146,12 +4225,6 @@ function mount(node, parent, renderer, anchor) {
4146
4225
  break;
4147
4226
  }
4148
4227
  }
4149
- function patchText(n1, n2, renderer) {
4150
- n2.elm = n1.elm;
4151
- if (n2.text !== n1.text) {
4152
- updateTextContent(n2, renderer);
4153
- }
4154
- }
4155
4228
  function mountText(vnode, parent, anchor, renderer) {
4156
4229
  const { owner } = vnode;
4157
4230
  const { createText } = renderer;
@@ -4164,7 +4237,7 @@ function patchComment(n1, n2, renderer) {
4164
4237
  // FIXME: Comment nodes should be static, we shouldn't need to diff them together. However
4165
4238
  // it is the case today.
4166
4239
  if (n2.text !== n1.text) {
4167
- updateTextContent(n2, renderer);
4240
+ updateTextContent$1(n2, renderer);
4168
4241
  }
4169
4242
  }
4170
4243
  function mountComment(vnode, parent, anchor, renderer) {
@@ -4208,7 +4281,7 @@ function patchStatic(n1, n2, renderer) {
4208
4281
  // slotAssignments can only apply to the top level element, never to a static part.
4209
4282
  patchSlotAssignment(n1, n2, renderer);
4210
4283
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
4211
- patchStaticParts(n1, n2);
4284
+ patchStaticParts(n1, n2, renderer);
4212
4285
  }
4213
4286
  function patchElement(n1, n2, renderer) {
4214
4287
  const elm = (n2.elm = n1.elm);
@@ -4219,19 +4292,20 @@ function mountStatic(vnode, parent, anchor, renderer) {
4219
4292
  const { owner } = vnode;
4220
4293
  const { cloneNode, isSyntheticShadowDefined } = renderer;
4221
4294
  const elm = (vnode.elm = cloneNode(vnode.fragment, true));
4295
+ // Define the root node shadow resolver
4222
4296
  linkNodeToShadow(elm, owner, renderer);
4223
4297
  applyElementRestrictions(elm, vnode);
4224
- // Marks this node as Static to propagate the shadow resolver. must happen after elm is assigned to the proper shadow
4225
4298
  const { renderMode, shadowMode } = owner;
4226
4299
  if (isSyntheticShadowDefined) {
4300
+ // Marks this node as Static to propagate the shadow resolver. must happen after elm is assigned to the proper shadow
4227
4301
  if (shadowMode === 1 /* ShadowMode.Synthetic */ || renderMode === 0 /* RenderMode.Light */) {
4228
4302
  elm[shared.KEY__SHADOW_STATIC] = true;
4229
4303
  }
4230
4304
  }
4231
4305
  // slotAssignments can only apply to the top level element, never to a static part.
4232
4306
  patchSlotAssignment(null, vnode, renderer);
4233
- insertNode(elm, parent, anchor, renderer);
4234
4307
  mountStaticParts(elm, vnode, renderer);
4308
+ insertNode(elm, parent, anchor, renderer);
4235
4309
  }
4236
4310
  function mountCustomElement(vnode, parent, anchor, renderer) {
4237
4311
  const { sel, owner, ctor } = vnode;
@@ -4396,17 +4470,6 @@ function linkNodeToShadow(elm, owner, renderer) {
4396
4470
  }
4397
4471
  }
4398
4472
  }
4399
- function updateTextContent(vnode, renderer) {
4400
- const { elm, text } = vnode;
4401
- const { setText } = renderer;
4402
- if (process.env.NODE_ENV !== 'production') {
4403
- unlockDomMutation();
4404
- }
4405
- setText(elm, text);
4406
- if (process.env.NODE_ENV !== 'production') {
4407
- lockDomMutation();
4408
- }
4409
- }
4410
4473
  function insertFragmentOrNode(vnode, parent, anchor, renderer) {
4411
4474
  if (process.env.NODE_ENV !== 'production') {
4412
4475
  unlockDomMutation();
@@ -4451,15 +4514,16 @@ function patchElementPropsAndAttrsAndRefs$1(oldVnode, vnode, renderer) {
4451
4514
  applyStaticClassAttribute(vnode, renderer);
4452
4515
  applyStaticStyleAttribute(vnode, renderer);
4453
4516
  }
4517
+ const { owner } = vnode;
4454
4518
  // Attrs need to be applied to element before props IE11 will wipe out value on radio inputs if
4455
4519
  // value is set before type=radio.
4456
4520
  patchClassAttribute(oldVnode, vnode, renderer);
4457
- patchStyleAttribute(oldVnode, vnode, renderer);
4521
+ patchStyleAttribute(oldVnode, vnode, renderer, owner);
4458
4522
  patchAttributes(oldVnode, vnode, renderer);
4459
4523
  patchProps(oldVnode, vnode, renderer);
4460
4524
  patchSlotAssignment(oldVnode, vnode, renderer);
4461
4525
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
4462
- applyRefs(vnode, vnode.owner);
4526
+ applyRefs(vnode, owner);
4463
4527
  }
4464
4528
  function applyStyleScoping(elm, owner, renderer) {
4465
4529
  const { getClassList } = renderer;
@@ -4861,10 +4925,14 @@ function addVNodeToChildLWC(vnode) {
4861
4925
  shared.ArrayPush.call(getVMBeingRendered().velements, vnode);
4862
4926
  }
4863
4927
  // [s]tatic [p]art
4864
- 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 */;
4865
4931
  return {
4932
+ type,
4866
4933
  partId,
4867
4934
  data,
4935
+ text,
4868
4936
  elm: undefined, // elm is defined later
4869
4937
  };
4870
4938
  }
@@ -4881,8 +4949,9 @@ function ssf(slotName, factory) {
4881
4949
  };
4882
4950
  }
4883
4951
  // [st]atic node
4884
- function st(fragment, key, parts) {
4952
+ function st(fragmentFactory, key, parts) {
4885
4953
  const owner = getVMBeingRendered();
4954
+ const fragment = fragmentFactory(parts);
4886
4955
  const vnode = {
4887
4956
  type: 4 /* VNodeType.Static */,
4888
4957
  sel: undefined,
@@ -4924,9 +4993,6 @@ function h(sel, data, children = EmptyArray) {
4924
4993
  // checking reserved internal data properties
4925
4994
  shared.assert.isFalse(data.className && data.classMap, `vnode.data.className and vnode.data.classMap ambiguous declaration.`);
4926
4995
  shared.assert.isFalse(data.styleDecls && data.style, `vnode.data.styleDecls and vnode.data.style ambiguous declaration.`);
4927
- if (data.style && !shared.isString(data.style)) {
4928
- logError(`Invalid 'style' attribute passed to <${sel}> is ignored. This attribute must be a string value.`, vmBeingRendered);
4929
- }
4930
4996
  shared.forEach.call(children, (childVnode) => {
4931
4997
  if (childVnode != null) {
4932
4998
  shared.assert.isTrue('type' in childVnode &&
@@ -4936,8 +5002,6 @@ function h(sel, data, children = EmptyArray) {
4936
5002
  }
4937
5003
  });
4938
5004
  }
4939
- // TODO [#3974]: remove temporary logic to support v5 compiler + v6+ engine
4940
- data = applyTemporaryCompilerV5SlotFix(data);
4941
5005
  const { key, slotAssignment } = data;
4942
5006
  const vnode = {
4943
5007
  type: 2 /* VNodeType.Element */,
@@ -4974,8 +5038,6 @@ function s(slotName, data, children, slotset) {
4974
5038
  }
4975
5039
  const vmBeingRendered = getVMBeingRendered();
4976
5040
  const { renderMode, apiVersion } = vmBeingRendered;
4977
- // TODO [#3974]: remove temporary logic to support v5 compiler + v6+ engine
4978
- data = applyTemporaryCompilerV5SlotFix(data);
4979
5041
  if (!shared.isUndefined(slotset) &&
4980
5042
  !shared.isUndefined(slotset.slotAssignments) &&
4981
5043
  !shared.isUndefined(slotset.slotAssignments[slotName]) &&
@@ -5079,8 +5141,6 @@ function c(sel, Ctor, data, children = EmptyArray) {
5079
5141
  });
5080
5142
  }
5081
5143
  }
5082
- // TODO [#3974]: remove temporary logic to support v5 compiler + v6+ engine
5083
- data = applyTemporaryCompilerV5SlotFix(data);
5084
5144
  const { key, slotAssignment } = data;
5085
5145
  let elm, aChildren, vm;
5086
5146
  const vnode = {
@@ -5142,16 +5202,19 @@ function i(iterable, factory) {
5142
5202
  if (process.env.NODE_ENV !== 'production') {
5143
5203
  const vnodes = shared.isArray(vnode) ? vnode : [vnode];
5144
5204
  shared.forEach.call(vnodes, (childVnode) => {
5145
- 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))) {
5146
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;
5147
5210
  if (shared.isString(key) || shared.isNumber(key)) {
5148
5211
  if (keyMap[key] === 1 && shared.isUndefined(iterationError)) {
5149
- 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.`;
5150
5213
  }
5151
5214
  keyMap[key] = 1;
5152
5215
  }
5153
5216
  else if (shared.isUndefined(iterationError)) {
5154
- 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.`;
5155
5218
  }
5156
5219
  }
5157
5220
  });
@@ -5550,6 +5613,111 @@ function validateLightDomTemplate(template, vm) {
5550
5613
  }
5551
5614
  }
5552
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
+ }
5553
5721
  // This should be a no-op outside of LWC's Karma tests, where it's not needed
5554
5722
  let registerFragmentCache = shared.noop;
5555
5723
  // Only used in LWC's Karma tests
@@ -5571,7 +5739,7 @@ function buildParseFragmentFn(createFragmentFn) {
5571
5739
  return (strings, ...keys) => {
5572
5740
  const cache = shared.create(null);
5573
5741
  registerFragmentCache(cache);
5574
- return function () {
5742
+ return function (parts) {
5575
5743
  const { context: { hasScopedStyles, stylesheetToken, legacyStylesheetToken }, shadowMode, renderer, } = getVMBeingRendered();
5576
5744
  const hasStyleToken = !shared.isUndefined(stylesheetToken);
5577
5745
  const isSyntheticShadow = shadowMode === 1 /* ShadowMode.Synthetic */;
@@ -5583,14 +5751,27 @@ function buildParseFragmentFn(createFragmentFn) {
5583
5751
  if (hasStyleToken && isSyntheticShadow) {
5584
5752
  cacheKey |= 2 /* FragmentCache.SHADOW_MODE_SYNTHETIC */;
5585
5753
  }
5586
- if (!shared.isUndefined(cache[cacheKey])) {
5587
- 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
+ }
5588
5761
  }
5589
5762
  // If legacy stylesheet tokens are required, then add them to the rendered string
5590
5763
  const stylesheetTokenToRender = stylesheetToken + (hasLegacyToken ? ` ${legacyStylesheetToken}` : '');
5591
5764
  const classToken = hasScopedStyles && hasStyleToken ? ' ' + stylesheetTokenToRender : '';
5592
5765
  const classAttrToken = hasScopedStyles && hasStyleToken ? ` class="${stylesheetTokenToRender}"` : '';
5593
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);
5594
5775
  let htmlFragment = '';
5595
5776
  for (let i = 0, n = keys.length; i < n; i++) {
5596
5777
  switch (keys[i]) {
@@ -5606,6 +5787,10 @@ function buildParseFragmentFn(createFragmentFn) {
5606
5787
  case 3: // ${1}${2}
5607
5788
  htmlFragment += strings[i] + classAttrToken + attrToken;
5608
5789
  break;
5790
+ default: // expressions ${partId:attributeName/textId}
5791
+ htmlFragment +=
5792
+ strings[i] + serializeExpression(keys[i], exprClassToken);
5793
+ break;
5609
5794
  }
5610
5795
  }
5611
5796
  htmlFragment += strings[strings.length - 1];
@@ -6488,25 +6673,25 @@ function forceRehydration(vm) {
6488
6673
  scheduleRehydration(vm);
6489
6674
  }
6490
6675
  }
6491
- function runFormAssociatedCustomElementCallback(vm, faceCb) {
6676
+ function runFormAssociatedCustomElementCallback(vm, faceCb, args) {
6492
6677
  const { renderMode, shadowMode } = vm;
6493
6678
  if (shadowMode === 1 /* ShadowMode.Synthetic */ && renderMode !== 0 /* RenderMode.Light */) {
6494
6679
  throw new Error('Form associated lifecycle methods are not available in synthetic shadow. Please use native shadow or light DOM.');
6495
6680
  }
6496
- invokeComponentCallback(vm, faceCb);
6681
+ invokeComponentCallback(vm, faceCb, args);
6497
6682
  }
6498
- function runFormAssociatedCallback(elm) {
6683
+ function runFormAssociatedCallback(elm, form) {
6499
6684
  const vm = getAssociatedVM(elm);
6500
6685
  const { formAssociatedCallback } = vm.def;
6501
6686
  if (!shared.isUndefined(formAssociatedCallback)) {
6502
- runFormAssociatedCustomElementCallback(vm, formAssociatedCallback);
6687
+ runFormAssociatedCustomElementCallback(vm, formAssociatedCallback, [form]);
6503
6688
  }
6504
6689
  }
6505
- function runFormDisabledCallback(elm) {
6690
+ function runFormDisabledCallback(elm, disabled) {
6506
6691
  const vm = getAssociatedVM(elm);
6507
6692
  const { formDisabledCallback } = vm.def;
6508
6693
  if (!shared.isUndefined(formDisabledCallback)) {
6509
- runFormAssociatedCustomElementCallback(vm, formDisabledCallback);
6694
+ runFormAssociatedCustomElementCallback(vm, formDisabledCallback, [disabled]);
6510
6695
  }
6511
6696
  }
6512
6697
  function runFormResetCallback(elm) {
@@ -6516,11 +6701,11 @@ function runFormResetCallback(elm) {
6516
6701
  runFormAssociatedCustomElementCallback(vm, formResetCallback);
6517
6702
  }
6518
6703
  }
6519
- function runFormStateRestoreCallback(elm) {
6704
+ function runFormStateRestoreCallback(elm, state, reason) {
6520
6705
  const vm = getAssociatedVM(elm);
6521
6706
  const { formStateRestoreCallback } = vm.def;
6522
6707
  if (!shared.isUndefined(formStateRestoreCallback)) {
6523
- runFormAssociatedCustomElementCallback(vm, formStateRestoreCallback);
6708
+ runFormAssociatedCustomElementCallback(vm, formStateRestoreCallback, [state, reason]);
6524
6709
  }
6525
6710
  }
6526
6711
  function resetRefVNodes(vm) {
@@ -6888,9 +7073,12 @@ function hydrateText(node, vnode, renderer) {
6888
7073
  if (!hasCorrectNodeType(vnode, node, 3 /* EnvNodeTypes.TEXT */, renderer)) {
6889
7074
  return handleMismatch(node, vnode, renderer);
6890
7075
  }
7076
+ return updateTextContent(node, vnode, vnode.owner, renderer);
7077
+ }
7078
+ function updateTextContent(node, vnode, owner, renderer) {
6891
7079
  if (process.env.NODE_ENV !== 'production') {
6892
7080
  if (!textNodeContentsAreEqual(node, vnode, renderer)) {
6893
- 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);
6894
7082
  }
6895
7083
  }
6896
7084
  const { setText } = renderer;
@@ -6916,11 +7104,24 @@ function hydrateComment(node, vnode, renderer) {
6916
7104
  }
6917
7105
  function hydrateStaticElement(elm, vnode, renderer) {
6918
7106
  if (!hasCorrectNodeType(vnode, elm, 1 /* EnvNodeTypes.ELEMENT */, renderer) ||
6919
- !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)) {
6920
7119
  return handleMismatch(elm, vnode, renderer);
6921
7120
  }
6922
7121
  vnode.elm = elm;
6923
- 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);
6924
7125
  return elm;
6925
7126
  }
6926
7127
  function hydrateFragment(elm, vnode, renderer) {
@@ -7096,12 +7297,13 @@ function isMatchingElement(vnode, elm, renderer, shouldValidateAttr = () => true
7096
7297
  }
7097
7298
  return false;
7098
7299
  }
7099
- const hasCompatibleAttrs = validateAttrs(vnode, elm, renderer, shouldValidateAttr);
7300
+ const { data } = vnode;
7301
+ const hasCompatibleAttrs = validateAttrs(vnode, elm, data, renderer, shouldValidateAttr);
7100
7302
  const hasCompatibleClass = shouldValidateAttr('class')
7101
- ? validateClassAttr(vnode, elm, renderer)
7303
+ ? validateClassAttr(vnode, elm, data, renderer)
7102
7304
  : true;
7103
7305
  const hasCompatibleStyle = shouldValidateAttr('style')
7104
- ? validateStyleAttr(vnode, elm, renderer)
7306
+ ? validateStyleAttr(vnode, elm, data, renderer)
7105
7307
  : true;
7106
7308
  return hasCompatibleAttrs && hasCompatibleClass && hasCompatibleStyle;
7107
7309
  }
@@ -7118,8 +7320,8 @@ function attributeValuesAreEqual(vnodeValue, value) {
7118
7320
  // In all other cases, the two values are not considered equal
7119
7321
  return false;
7120
7322
  }
7121
- function validateAttrs(vnode, elm, renderer, shouldValidateAttr) {
7122
- const { data: { attrs = {} }, } = vnode;
7323
+ function validateAttrs(vnode, elm, data, renderer, shouldValidateAttr) {
7324
+ const { attrs = {} } = data;
7123
7325
  let nodesAreCompatible = true;
7124
7326
  // Validate attributes, though we could always recovery from those by running the update mods.
7125
7327
  // Note: intentionally ONLY matching vnodes.attrs to elm.attrs, in case SSR is adding extra attributes.
@@ -7127,21 +7329,22 @@ function validateAttrs(vnode, elm, renderer, shouldValidateAttr) {
7127
7329
  if (!shouldValidateAttr(attrName)) {
7128
7330
  continue;
7129
7331
  }
7130
- const { owner } = vnode;
7131
7332
  const { getAttribute } = renderer;
7132
7333
  const elmAttrValue = getAttribute(elm, attrName);
7133
7334
  if (!attributeValuesAreEqual(attrValue, elmAttrValue)) {
7134
7335
  if (process.env.NODE_ENV !== 'production') {
7135
7336
  const { getProperty } = renderer;
7136
- 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);
7137
7338
  }
7138
7339
  nodesAreCompatible = false;
7139
7340
  }
7140
7341
  }
7141
7342
  return nodesAreCompatible;
7142
7343
  }
7143
- function validateClassAttr(vnode, elm, renderer) {
7144
- 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.
7145
7348
  let { className, classMap } = data;
7146
7349
  const { getProperty, getClassList, getAttribute } = renderer;
7147
7350
  // we don't care about legacy for hydration. it's a new use case
@@ -7214,8 +7417,9 @@ function validateClassAttr(vnode, elm, renderer) {
7214
7417
  }
7215
7418
  return nodesAreCompatible;
7216
7419
  }
7217
- function validateStyleAttr(vnode, elm, renderer) {
7218
- 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;
7219
7423
  const { getAttribute } = renderer;
7220
7424
  const elmStyle = getAttribute(elm, 'style') || '';
7221
7425
  let vnodeStyle;
@@ -7255,7 +7459,7 @@ function validateStyleAttr(vnode, elm, renderer) {
7255
7459
  }
7256
7460
  return nodesAreCompatible;
7257
7461
  }
7258
- function areCompatibleNodes(client, ssr, vnode, renderer) {
7462
+ function areCompatibleStaticNodes(client, ssr, vnode, renderer) {
7259
7463
  const { getProperty, getAttribute } = renderer;
7260
7464
  if (getProperty(client, 'nodeType') === 3 /* EnvNodeTypes.TEXT */) {
7261
7465
  if (!hasCorrectNodeType(vnode, ssr, 3 /* EnvNodeTypes.TEXT */, renderer)) {
@@ -7272,24 +7476,63 @@ function areCompatibleNodes(client, ssr, vnode, renderer) {
7272
7476
  if (!hasCorrectNodeType(vnode, ssr, 1 /* EnvNodeTypes.ELEMENT */, renderer)) {
7273
7477
  return false;
7274
7478
  }
7479
+ const { owner, parts } = vnode;
7275
7480
  let isCompatibleElements = true;
7276
7481
  if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) {
7277
7482
  if (process.env.NODE_ENV !== 'production') {
7278
- 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);
7279
7484
  }
7280
7485
  return false;
7281
7486
  }
7282
7487
  const clientAttrsNames = getProperty(client, 'getAttributeNames').call(client);
7283
7488
  clientAttrsNames.forEach((attrName) => {
7284
7489
  if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) {
7285
- if (process.env.NODE_ENV !== 'production') {
7286
- 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;
7287
7499
  }
7288
- isCompatibleElements = false;
7289
7500
  }
7290
7501
  });
7291
7502
  return isCompatibleElements;
7292
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
+ }
7293
7536
 
7294
7537
  /*
7295
7538
  * Copyright (c) 2018, salesforce.com, inc.
@@ -7604,5 +7847,5 @@ exports.swapTemplate = swapTemplate;
7604
7847
  exports.track = track;
7605
7848
  exports.unwrap = unwrap;
7606
7849
  exports.wire = wire;
7607
- /** version: 6.3.3 */
7850
+ /** version: 6.4.0 */
7608
7851
  //# sourceMappingURL=index.cjs.js.map