@lwc/engine-core 2.5.1 → 2.5.2-canary1

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.
@@ -4012,7 +4012,6 @@ function setScopeTokenClassIfNecessary(elm, owner) {
4012
4012
  owner.renderer.getClassList(elm).add(token);
4013
4013
  }
4014
4014
  }
4015
-
4016
4015
  function updateNodeHook(oldVnode, vnode) {
4017
4016
  const {
4018
4017
  elm,
@@ -4076,6 +4075,16 @@ function createElmHook(vnode) {
4076
4075
  modComputedClassName.create(vnode);
4077
4076
  modComputedStyle.create(vnode);
4078
4077
  }
4078
+ function hydrateElmHook(vnode) {
4079
+ modEvents.create(vnode); // Attrs are already on the element.
4080
+ // modAttrs.create(vnode);
4081
+
4082
+ modProps.create(vnode); // Already set.
4083
+ // modStaticClassName.create(vnode);
4084
+ // modStaticStyle.create(vnode);
4085
+ // modComputedClassName.create(vnode);
4086
+ // modComputedStyle.create(vnode);
4087
+ }
4079
4088
  function fallbackElmHook(elm, vnode) {
4080
4089
  const {
4081
4090
  owner
@@ -4240,6 +4249,172 @@ function createChildrenHook(vnode) {
4240
4249
  }
4241
4250
  }
4242
4251
  }
4252
+
4253
+ function isElementNode(node) {
4254
+ // @todo: should the hydrate be part of engine-dom? can we move hydrate out of the hooks?
4255
+ // eslint-disable-next-line lwc-internal/no-global-node
4256
+ return node.nodeType === Node.ELEMENT_NODE;
4257
+ }
4258
+
4259
+ function vnodesAndElementHaveCompatibleAttrs(vnode, elm) {
4260
+ const {
4261
+ data: {
4262
+ attrs = {}
4263
+ },
4264
+ owner: {
4265
+ renderer
4266
+ }
4267
+ } = vnode;
4268
+ let nodesAreCompatible = true; // Validate attributes, though we could always recovery from those by running the update mods.
4269
+ // Note: intentionally ONLY matching vnodes.attrs to elm.attrs, in case SSR is adding extra attributes.
4270
+
4271
+ for (const [attrName, attrValue] of Object.entries(attrs)) {
4272
+ const elmAttrValue = renderer.getAttribute(elm, attrName);
4273
+
4274
+ if (attrValue !== elmAttrValue) {
4275
+ logError(`Error hydrating element: attribute "${attrName}" has different values, expected "${attrValue}" but found "${elmAttrValue}"`, vnode.owner);
4276
+ nodesAreCompatible = false;
4277
+ }
4278
+ }
4279
+
4280
+ return nodesAreCompatible;
4281
+ }
4282
+
4283
+ function vnodesAndElementHaveCompatibleClass(vnode, elm) {
4284
+ const {
4285
+ data: {
4286
+ className,
4287
+ classMap
4288
+ },
4289
+ owner: {
4290
+ renderer
4291
+ }
4292
+ } = vnode;
4293
+ let nodesAreCompatible = true;
4294
+
4295
+ if (!shared.isUndefined(className) && className !== elm.className) {
4296
+ // @todo: not sure if the above comparison is correct, maybe we should normalize to classlist
4297
+ // className is used when class is bound to an expr.
4298
+ logError(`Mismatch hydrating element: attribute "class" has different values, expected "${className}" but found "${elm.className}"`, vnode.owner);
4299
+ nodesAreCompatible = false;
4300
+ } else if (!shared.isUndefined(classMap)) {
4301
+ // classMap is used when class is set to static value.
4302
+ // @todo: there might be a simpler method to do this.
4303
+ const classList = renderer.getClassList(elm);
4304
+ let hasClassMismatch = false;
4305
+ let computedClassName = ''; // all classes from the vnode should be in the element.classList
4306
+
4307
+ for (const name in classMap) {
4308
+ computedClassName += ' ' + name;
4309
+
4310
+ if (!classList.contains(name)) {
4311
+ nodesAreCompatible = false;
4312
+ hasClassMismatch = true;
4313
+ }
4314
+ } // all classes from element.classList should be in the vnode classMap
4315
+
4316
+
4317
+ classList.forEach(name => {
4318
+ if (!classMap[name]) {
4319
+ nodesAreCompatible = false;
4320
+ hasClassMismatch = true;
4321
+ }
4322
+ });
4323
+
4324
+ if (hasClassMismatch) {
4325
+ logError(`Mismatch hydrating element: attribute "class" has different values, expected "${computedClassName.trim()}" but found "${elm.className}"`, vnode.owner);
4326
+ }
4327
+ }
4328
+
4329
+ return nodesAreCompatible;
4330
+ }
4331
+
4332
+ function vnodesAndElementHaveCompatibleStyle(vnode, elm) {
4333
+ const {
4334
+ data: {
4335
+ style,
4336
+ styleDecls
4337
+ },
4338
+ owner: {
4339
+ renderer
4340
+ }
4341
+ } = vnode;
4342
+ const elmStyle = renderer.getAttribute(elm, 'style');
4343
+ let nodesAreCompatible = true; // @todo: question: would it be the same or is there a chance that the browser tweak the result of elm.setAttribute('style', ...)?
4344
+ // ex: such "str" exist that after elm.setAttribute('style', str), elm.getAttribute('style') !== str.
4345
+
4346
+ if (!shared.isUndefined(style) && style !== elmStyle) {
4347
+ // style is used when class is bound to an expr.
4348
+ logError(`Mismatch hydrating element: attribute "style" has different values, expected "${style}" but found "${elmStyle}".`, vnode.owner);
4349
+ nodesAreCompatible = false;
4350
+ } else if (!shared.isUndefined(styleDecls)) {
4351
+ // styleMap is used when class is set to static value.
4352
+ for (let i = 0; i < styleDecls.length; i++) {
4353
+ const [prop, value, important] = styleDecls[i];
4354
+ const elmPropValue = elm.style.getPropertyValue(prop);
4355
+ const elmPropPriority = elm.style.getPropertyPriority(prop);
4356
+
4357
+ if (value !== elmPropValue || important !== (elmPropPriority === 'important')) {
4358
+ nodesAreCompatible = false;
4359
+ }
4360
+ } // questions: is there a way to check that only those props in styleMap are set in the element?
4361
+ // how to generate the style?
4362
+
4363
+
4364
+ logError('Error hydrating element: attribute "style" has different values.', vnode.owner);
4365
+ }
4366
+
4367
+ return nodesAreCompatible;
4368
+ }
4369
+
4370
+ function throwHydrationError() {
4371
+ // @todo: maybe create a type for these type of hydration errors
4372
+ shared.assert.fail('Server rendered elements do not match client side generated elements');
4373
+ }
4374
+
4375
+ function hydrateChildrenHook(elmChildren, children, vm) {
4376
+ var _a;
4377
+
4378
+ if (process.env.NODE_ENV !== 'production') {
4379
+ const filteredVNodes = shared.ArrayFilter.call(children, vnode => !!vnode);
4380
+
4381
+ if (elmChildren.length !== filteredVNodes.length) {
4382
+ logError(`Hydration mismatch: incorrect number of rendered elements, expected ${filteredVNodes.length} but found ${elmChildren.length}.`, vm);
4383
+ throwHydrationError();
4384
+ }
4385
+ }
4386
+
4387
+ let elmCurrentChildIdx = 0;
4388
+
4389
+ for (let j = 0, n = children.length; j < n; j++) {
4390
+ const ch = children[j];
4391
+
4392
+ if (ch != null) {
4393
+ const childNode = elmChildren[elmCurrentChildIdx];
4394
+
4395
+ if (process.env.NODE_ENV !== 'production') {
4396
+ // VComments and VTexts validation is handled in their hooks
4397
+ if (isElementNode(childNode)) {
4398
+ if (((_a = ch.sel) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== childNode.tagName.toLowerCase()) {
4399
+ logError(`Hydration mismatch: expecting element with tag "${ch.sel}" but found "${childNode.tagName}".`, vm);
4400
+ throwHydrationError();
4401
+ } // Note: props are not yet set
4402
+
4403
+
4404
+ const isVNodeAndElementCompatible = vnodesAndElementHaveCompatibleAttrs(ch, childNode) && vnodesAndElementHaveCompatibleClass(ch, childNode) && vnodesAndElementHaveCompatibleStyle(ch, childNode);
4405
+
4406
+ if (!isVNodeAndElementCompatible) {
4407
+ logError(`Hydration mismatch: incompatible attributes for element with tag "${childNode.tagName}".`, vm);
4408
+ throwHydrationError();
4409
+ }
4410
+ }
4411
+ }
4412
+
4413
+ ch.hook.hydrate(ch, childNode);
4414
+ elmCurrentChildIdx++;
4415
+ }
4416
+ }
4417
+ }
4243
4418
  function updateCustomElmHook(oldVnode, vnode) {
4244
4419
  // Attrs need to be applied to element before props
4245
4420
  // IE11 will wipe out value on radio inputs if value
@@ -4336,7 +4511,27 @@ const TextHook = {
4336
4511
  update: updateNodeHook,
4337
4512
  insert: insertNodeHook,
4338
4513
  move: insertNodeHook,
4339
- remove: removeNodeHook
4514
+ remove: removeNodeHook,
4515
+ hydrate: (vNode, node) => {
4516
+ var _a; // @todo tests.
4517
+
4518
+
4519
+ if (process.env.NODE_ENV !== 'production') {
4520
+ // eslint-disable-next-line lwc-internal/no-global-node
4521
+ if (node.nodeType !== Node.TEXT_NODE) {
4522
+ logError('Hydration mismatch: incorrect node type received', vNode.owner);
4523
+ shared.assert.fail('Hydration mismatch: incorrect node type received.');
4524
+ }
4525
+
4526
+ if (node.nodeValue !== vNode.text) {
4527
+ logError('Hydration mismatch: text values do not match, will recover from the difference', vNode.owner);
4528
+ }
4529
+ } // always set the text value to the one from the vnode.
4530
+
4531
+
4532
+ node.nodeValue = (_a = vNode.text) !== null && _a !== void 0 ? _a : null;
4533
+ vNode.elm = node;
4534
+ }
4340
4535
  };
4341
4536
  const CommentHook = {
4342
4537
  create: vnode => {
@@ -4354,7 +4549,23 @@ const CommentHook = {
4354
4549
  update: updateNodeHook,
4355
4550
  insert: insertNodeHook,
4356
4551
  move: insertNodeHook,
4357
- remove: removeNodeHook
4552
+ remove: removeNodeHook,
4553
+ hydrate: (vNode, node) => {
4554
+ var _a; // @todo tests.
4555
+
4556
+
4557
+ if (process.env.NODE_ENV !== 'production') {
4558
+ // eslint-disable-next-line lwc-internal/no-global-node
4559
+ if (node.nodeType !== Node.COMMENT_NODE) {
4560
+ logError('Hydration mismatch: incorrect node type received', vNode.owner);
4561
+ shared.assert.fail('Hydration mismatch: incorrect node type received.');
4562
+ }
4563
+ } // always set the text value to the one from the vnode.
4564
+
4565
+
4566
+ node.nodeValue = (_a = vNode.text) !== null && _a !== void 0 ? _a : null;
4567
+ vNode.elm = node;
4568
+ }
4358
4569
  }; // insert is called after update, which is used somewhere else (via a module)
4359
4570
  // to mark the vm as inserted, that means we cannot use update as the main channel
4360
4571
  // to rehydrate when dirty, because sometimes the element is not inserted just yet,
@@ -4394,6 +4605,12 @@ const ElementHook = {
4394
4605
  remove: (vnode, parentNode) => {
4395
4606
  removeNodeHook(vnode, parentNode);
4396
4607
  removeElmHook(vnode);
4608
+ },
4609
+ hydrate: (vnode, node) => {
4610
+ vnode.elm = node;
4611
+ hydrateElmHook(vnode); // hydrate children hook
4612
+
4613
+ hydrateChildrenHook(vnode.elm.childNodes, vnode.children, vnode.owner);
4397
4614
  }
4398
4615
  };
4399
4616
  const CustomElementHook = {
@@ -4485,6 +4702,52 @@ const CustomElementHook = {
4485
4702
  // will take care of disconnecting any child VM attached to its shadow as well.
4486
4703
  removeVM(vm);
4487
4704
  }
4705
+ },
4706
+ hydrate: (vnode, elm) => {
4707
+ // the element is created, but the vm is not
4708
+ const {
4709
+ sel,
4710
+ mode,
4711
+ ctor,
4712
+ owner
4713
+ } = vnode;
4714
+ const def = getComponentInternalDef(ctor);
4715
+ createVM(elm, def, {
4716
+ mode,
4717
+ owner,
4718
+ tagName: sel,
4719
+ renderer: owner.renderer
4720
+ });
4721
+ vnode.elm = elm;
4722
+ const vm = getAssociatedVMIfPresent(elm);
4723
+
4724
+ if (vm) {
4725
+ allocateChildrenHook(vnode, vm);
4726
+ }
4727
+
4728
+ hydrateElmHook(vnode); // Insert hook section:
4729
+
4730
+ if (vm) {
4731
+ if (process.env.NODE_ENV !== 'production') {
4732
+ shared.assert.isTrue(vm.state === 0
4733
+ /* created */
4734
+ , `${vm} cannot be recycled.`);
4735
+ }
4736
+
4737
+ runConnectedCallback(vm);
4738
+ }
4739
+
4740
+ if (!(vm && vm.renderMode === 0
4741
+ /* Light */
4742
+ )) {
4743
+ // VM is not rendering in Light DOM, we can proceed and hydrate the slotted content.
4744
+ // Note: for Light DOM, this is handled while hydrating the VM
4745
+ hydrateChildrenHook(vnode.elm.childNodes, vnode.children, vm);
4746
+ }
4747
+
4748
+ if (vm) {
4749
+ hydrateVM(vm);
4750
+ }
4488
4751
  }
4489
4752
  };
4490
4753
 
@@ -5135,7 +5398,7 @@ function createStylesheet(vm, stylesheets) {
5135
5398
  for (let i = 0; i < stylesheets.length; i++) {
5136
5399
  renderer.insertGlobalStylesheet(stylesheets[i]);
5137
5400
  }
5138
- } else if (renderer.ssr) {
5401
+ } else if (renderer.ssr || renderer.isHydrating) {
5139
5402
  // native shadow or light DOM, SSR
5140
5403
  const combinedStylesheetContent = shared.ArrayJoin.call(stylesheets, '\n');
5141
5404
  return createInlineStyleVNode(combinedStylesheetContent);
@@ -5321,9 +5584,9 @@ function validateLightDomTemplate(template, vm) {
5321
5584
  if (vm.renderMode === 0
5322
5585
  /* Light */
5323
5586
  ) {
5324
- shared.assert.isTrue(template.renderMode === 'light', `Light DOM components can't render shadow DOM templates. Add an 'lwc:render-mode="light"' directive on the root template tag.`);
5587
+ shared.assert.isTrue(template.renderMode === 'light', `Light DOM components can't render shadow DOM templates. Add an 'lwc:render-mode="light"' directive to the root template tag of ${getComponentTag(vm)}.`);
5325
5588
  } else {
5326
- shared.assert.isTrue(shared.isUndefined(template.renderMode), `Shadow DOM components template can't render light DOM templates. Either remove the 'lwc:render-mode' directive or set it to 'lwc:render-mode="shadow"`);
5589
+ shared.assert.isTrue(shared.isUndefined(template.renderMode), `Shadow DOM components template can't render light DOM templates. Either remove the 'lwc:render-mode' directive from ${getComponentTag(vm)} or set it to 'lwc:render-mode="shadow"`);
5327
5590
  }
5328
5591
  }
5329
5592
 
@@ -5732,12 +5995,28 @@ function connectRootElement(elm) {
5732
5995
  /* GlobalHydrate */
5733
5996
  , vm);
5734
5997
  }
5998
+ function hydrateRootElement(elm) {
5999
+ const vm = getAssociatedVM(elm); // Usually means moving the element from one place to another, which is observable via
6000
+ // life-cycle hooks.
6001
+
6002
+ if (vm.state === 1
6003
+ /* connected */
6004
+ ) {
6005
+ disconnectRootElement(elm);
6006
+ }
6007
+
6008
+ runConnectedCallback(vm);
6009
+ hydrateVM(vm); // should we hydrate the root children? light dom case.
6010
+ }
5735
6011
  function disconnectRootElement(elm) {
5736
6012
  const vm = getAssociatedVM(elm);
5737
6013
  resetComponentStateWhenRemoved(vm);
5738
6014
  }
5739
6015
  function appendVM(vm) {
5740
6016
  rehydrate(vm);
6017
+ }
6018
+ function hydrateVM(vm) {
6019
+ hydrate(vm);
5741
6020
  } // just in case the component comes back, with this we guarantee re-rendering it
5742
6021
  // while preventing any attempt to rehydration until after reinsertion.
5743
6022
 
@@ -5962,6 +6241,22 @@ function rehydrate(vm) {
5962
6241
  }
5963
6242
  }
5964
6243
 
6244
+ function hydrate(vm) {
6245
+ if (shared.isTrue(vm.isDirty)) {
6246
+ // manually diffing/patching here.
6247
+ // This routine is:
6248
+ // patchShadowRoot(vm, children);
6249
+ // -> addVnodes.
6250
+ const children = renderComponent(vm);
6251
+ vm.children = children;
6252
+ const vmChildren = vm.renderMode === 0
6253
+ /* Light */
6254
+ ? vm.elm.childNodes : vm.elm.shadowRoot.childNodes;
6255
+ hydrateChildrenHook(vmChildren, children, vm);
6256
+ runRenderedCallback(vm);
6257
+ }
6258
+ }
6259
+
5965
6260
  function patchShadowRoot(vm, newCh) {
5966
6261
  const {
5967
6262
  children: oldCh
@@ -6788,6 +7083,7 @@ exports.getAssociatedVMIfPresent = getAssociatedVMIfPresent;
6788
7083
  exports.getComponentDef = getComponentDef;
6789
7084
  exports.getComponentInternalDef = getComponentInternalDef;
6790
7085
  exports.getUpgradableConstructor = getUpgradableConstructor;
7086
+ exports.hydrateRootElement = hydrateRootElement;
6791
7087
  exports.isComponentConstructor = isComponentConstructor;
6792
7088
  exports.readonly = readonly;
6793
7089
  exports.register = register;
@@ -6801,4 +7097,4 @@ exports.swapTemplate = swapTemplate;
6801
7097
  exports.track = track;
6802
7098
  exports.unwrap = unwrap;
6803
7099
  exports.wire = wire;
6804
- /* version: 2.5.1 */
7100
+ /* version: 2.5.2-canary1 */
@@ -1,5 +1,5 @@
1
1
  /* proxy-compat-disable */
2
- import { seal, create, isFunction as isFunction$1, ArrayPush as ArrayPush$1, isUndefined as isUndefined$1, ArrayIndexOf, ArraySplice, StringToLowerCase, ArrayJoin, isNull, assign, assert, keys, StringCharCodeAt, isString, StringSlice, freeze, defineProperties, forEach, getOwnPropertyNames as getOwnPropertyNames$1, getPrototypeOf as getPrototypeOf$1, setPrototypeOf, getPropertyDescriptor, isObject, AriaPropNameToAttrNameMap, defineProperty, KEY__SYNTHETIC_MODE, toString as toString$1, isFalse, isTrue, getOwnPropertyDescriptor as getOwnPropertyDescriptor$1, htmlPropertyToAttribute, ArraySlice, hasOwnProperty as hasOwnProperty$1, noop, isArray as isArray$1, isNumber, StringReplace, KEY__SHADOW_RESOLVER, KEY__SCOPED_CSS, ArrayUnshift, isFrozen } from '@lwc/shared';
2
+ import { seal, create, isFunction as isFunction$1, ArrayPush as ArrayPush$1, isUndefined as isUndefined$1, ArrayIndexOf, ArraySplice, StringToLowerCase, ArrayJoin, isNull, assign, assert, keys, StringCharCodeAt, isString, StringSlice, freeze, defineProperties, forEach, getOwnPropertyNames as getOwnPropertyNames$1, getPrototypeOf as getPrototypeOf$1, setPrototypeOf, getPropertyDescriptor, isObject, AriaPropNameToAttrNameMap, defineProperty, KEY__SYNTHETIC_MODE, toString as toString$1, isFalse, isTrue, getOwnPropertyDescriptor as getOwnPropertyDescriptor$1, htmlPropertyToAttribute, ArraySlice, hasOwnProperty as hasOwnProperty$1, ArrayFilter, noop, isArray as isArray$1, isNumber, StringReplace, KEY__SHADOW_RESOLVER, KEY__SCOPED_CSS, ArrayUnshift, isFrozen } from '@lwc/shared';
3
3
  import { runtimeFlags } from '@lwc/features';
4
4
  export { setFeatureFlag, setFeatureFlagForTest } from '@lwc/features';
5
5
 
@@ -4009,7 +4009,6 @@ function setScopeTokenClassIfNecessary(elm, owner) {
4009
4009
  owner.renderer.getClassList(elm).add(token);
4010
4010
  }
4011
4011
  }
4012
-
4013
4012
  function updateNodeHook(oldVnode, vnode) {
4014
4013
  const {
4015
4014
  elm,
@@ -4073,6 +4072,16 @@ function createElmHook(vnode) {
4073
4072
  modComputedClassName.create(vnode);
4074
4073
  modComputedStyle.create(vnode);
4075
4074
  }
4075
+ function hydrateElmHook(vnode) {
4076
+ modEvents.create(vnode); // Attrs are already on the element.
4077
+ // modAttrs.create(vnode);
4078
+
4079
+ modProps.create(vnode); // Already set.
4080
+ // modStaticClassName.create(vnode);
4081
+ // modStaticStyle.create(vnode);
4082
+ // modComputedClassName.create(vnode);
4083
+ // modComputedStyle.create(vnode);
4084
+ }
4076
4085
  function fallbackElmHook(elm, vnode) {
4077
4086
  const {
4078
4087
  owner
@@ -4237,6 +4246,172 @@ function createChildrenHook(vnode) {
4237
4246
  }
4238
4247
  }
4239
4248
  }
4249
+
4250
+ function isElementNode(node) {
4251
+ // @todo: should the hydrate be part of engine-dom? can we move hydrate out of the hooks?
4252
+ // eslint-disable-next-line lwc-internal/no-global-node
4253
+ return node.nodeType === Node.ELEMENT_NODE;
4254
+ }
4255
+
4256
+ function vnodesAndElementHaveCompatibleAttrs(vnode, elm) {
4257
+ const {
4258
+ data: {
4259
+ attrs = {}
4260
+ },
4261
+ owner: {
4262
+ renderer
4263
+ }
4264
+ } = vnode;
4265
+ let nodesAreCompatible = true; // Validate attributes, though we could always recovery from those by running the update mods.
4266
+ // Note: intentionally ONLY matching vnodes.attrs to elm.attrs, in case SSR is adding extra attributes.
4267
+
4268
+ for (const [attrName, attrValue] of Object.entries(attrs)) {
4269
+ const elmAttrValue = renderer.getAttribute(elm, attrName);
4270
+
4271
+ if (attrValue !== elmAttrValue) {
4272
+ logError(`Error hydrating element: attribute "${attrName}" has different values, expected "${attrValue}" but found "${elmAttrValue}"`, vnode.owner);
4273
+ nodesAreCompatible = false;
4274
+ }
4275
+ }
4276
+
4277
+ return nodesAreCompatible;
4278
+ }
4279
+
4280
+ function vnodesAndElementHaveCompatibleClass(vnode, elm) {
4281
+ const {
4282
+ data: {
4283
+ className,
4284
+ classMap
4285
+ },
4286
+ owner: {
4287
+ renderer
4288
+ }
4289
+ } = vnode;
4290
+ let nodesAreCompatible = true;
4291
+
4292
+ if (!isUndefined$1(className) && className !== elm.className) {
4293
+ // @todo: not sure if the above comparison is correct, maybe we should normalize to classlist
4294
+ // className is used when class is bound to an expr.
4295
+ logError(`Mismatch hydrating element: attribute "class" has different values, expected "${className}" but found "${elm.className}"`, vnode.owner);
4296
+ nodesAreCompatible = false;
4297
+ } else if (!isUndefined$1(classMap)) {
4298
+ // classMap is used when class is set to static value.
4299
+ // @todo: there might be a simpler method to do this.
4300
+ const classList = renderer.getClassList(elm);
4301
+ let hasClassMismatch = false;
4302
+ let computedClassName = ''; // all classes from the vnode should be in the element.classList
4303
+
4304
+ for (const name in classMap) {
4305
+ computedClassName += ' ' + name;
4306
+
4307
+ if (!classList.contains(name)) {
4308
+ nodesAreCompatible = false;
4309
+ hasClassMismatch = true;
4310
+ }
4311
+ } // all classes from element.classList should be in the vnode classMap
4312
+
4313
+
4314
+ classList.forEach(name => {
4315
+ if (!classMap[name]) {
4316
+ nodesAreCompatible = false;
4317
+ hasClassMismatch = true;
4318
+ }
4319
+ });
4320
+
4321
+ if (hasClassMismatch) {
4322
+ logError(`Mismatch hydrating element: attribute "class" has different values, expected "${computedClassName.trim()}" but found "${elm.className}"`, vnode.owner);
4323
+ }
4324
+ }
4325
+
4326
+ return nodesAreCompatible;
4327
+ }
4328
+
4329
+ function vnodesAndElementHaveCompatibleStyle(vnode, elm) {
4330
+ const {
4331
+ data: {
4332
+ style,
4333
+ styleDecls
4334
+ },
4335
+ owner: {
4336
+ renderer
4337
+ }
4338
+ } = vnode;
4339
+ const elmStyle = renderer.getAttribute(elm, 'style');
4340
+ let nodesAreCompatible = true; // @todo: question: would it be the same or is there a chance that the browser tweak the result of elm.setAttribute('style', ...)?
4341
+ // ex: such "str" exist that after elm.setAttribute('style', str), elm.getAttribute('style') !== str.
4342
+
4343
+ if (!isUndefined$1(style) && style !== elmStyle) {
4344
+ // style is used when class is bound to an expr.
4345
+ logError(`Mismatch hydrating element: attribute "style" has different values, expected "${style}" but found "${elmStyle}".`, vnode.owner);
4346
+ nodesAreCompatible = false;
4347
+ } else if (!isUndefined$1(styleDecls)) {
4348
+ // styleMap is used when class is set to static value.
4349
+ for (let i = 0; i < styleDecls.length; i++) {
4350
+ const [prop, value, important] = styleDecls[i];
4351
+ const elmPropValue = elm.style.getPropertyValue(prop);
4352
+ const elmPropPriority = elm.style.getPropertyPriority(prop);
4353
+
4354
+ if (value !== elmPropValue || important !== (elmPropPriority === 'important')) {
4355
+ nodesAreCompatible = false;
4356
+ }
4357
+ } // questions: is there a way to check that only those props in styleMap are set in the element?
4358
+ // how to generate the style?
4359
+
4360
+
4361
+ logError('Error hydrating element: attribute "style" has different values.', vnode.owner);
4362
+ }
4363
+
4364
+ return nodesAreCompatible;
4365
+ }
4366
+
4367
+ function throwHydrationError() {
4368
+ // @todo: maybe create a type for these type of hydration errors
4369
+ assert.fail('Server rendered elements do not match client side generated elements');
4370
+ }
4371
+
4372
+ function hydrateChildrenHook(elmChildren, children, vm) {
4373
+ var _a;
4374
+
4375
+ if (process.env.NODE_ENV !== 'production') {
4376
+ const filteredVNodes = ArrayFilter.call(children, vnode => !!vnode);
4377
+
4378
+ if (elmChildren.length !== filteredVNodes.length) {
4379
+ logError(`Hydration mismatch: incorrect number of rendered elements, expected ${filteredVNodes.length} but found ${elmChildren.length}.`, vm);
4380
+ throwHydrationError();
4381
+ }
4382
+ }
4383
+
4384
+ let elmCurrentChildIdx = 0;
4385
+
4386
+ for (let j = 0, n = children.length; j < n; j++) {
4387
+ const ch = children[j];
4388
+
4389
+ if (ch != null) {
4390
+ const childNode = elmChildren[elmCurrentChildIdx];
4391
+
4392
+ if (process.env.NODE_ENV !== 'production') {
4393
+ // VComments and VTexts validation is handled in their hooks
4394
+ if (isElementNode(childNode)) {
4395
+ if (((_a = ch.sel) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== childNode.tagName.toLowerCase()) {
4396
+ logError(`Hydration mismatch: expecting element with tag "${ch.sel}" but found "${childNode.tagName}".`, vm);
4397
+ throwHydrationError();
4398
+ } // Note: props are not yet set
4399
+
4400
+
4401
+ const isVNodeAndElementCompatible = vnodesAndElementHaveCompatibleAttrs(ch, childNode) && vnodesAndElementHaveCompatibleClass(ch, childNode) && vnodesAndElementHaveCompatibleStyle(ch, childNode);
4402
+
4403
+ if (!isVNodeAndElementCompatible) {
4404
+ logError(`Hydration mismatch: incompatible attributes for element with tag "${childNode.tagName}".`, vm);
4405
+ throwHydrationError();
4406
+ }
4407
+ }
4408
+ }
4409
+
4410
+ ch.hook.hydrate(ch, childNode);
4411
+ elmCurrentChildIdx++;
4412
+ }
4413
+ }
4414
+ }
4240
4415
  function updateCustomElmHook(oldVnode, vnode) {
4241
4416
  // Attrs need to be applied to element before props
4242
4417
  // IE11 will wipe out value on radio inputs if value
@@ -4333,7 +4508,27 @@ const TextHook = {
4333
4508
  update: updateNodeHook,
4334
4509
  insert: insertNodeHook,
4335
4510
  move: insertNodeHook,
4336
- remove: removeNodeHook
4511
+ remove: removeNodeHook,
4512
+ hydrate: (vNode, node) => {
4513
+ var _a; // @todo tests.
4514
+
4515
+
4516
+ if (process.env.NODE_ENV !== 'production') {
4517
+ // eslint-disable-next-line lwc-internal/no-global-node
4518
+ if (node.nodeType !== Node.TEXT_NODE) {
4519
+ logError('Hydration mismatch: incorrect node type received', vNode.owner);
4520
+ assert.fail('Hydration mismatch: incorrect node type received.');
4521
+ }
4522
+
4523
+ if (node.nodeValue !== vNode.text) {
4524
+ logError('Hydration mismatch: text values do not match, will recover from the difference', vNode.owner);
4525
+ }
4526
+ } // always set the text value to the one from the vnode.
4527
+
4528
+
4529
+ node.nodeValue = (_a = vNode.text) !== null && _a !== void 0 ? _a : null;
4530
+ vNode.elm = node;
4531
+ }
4337
4532
  };
4338
4533
  const CommentHook = {
4339
4534
  create: vnode => {
@@ -4351,7 +4546,23 @@ const CommentHook = {
4351
4546
  update: updateNodeHook,
4352
4547
  insert: insertNodeHook,
4353
4548
  move: insertNodeHook,
4354
- remove: removeNodeHook
4549
+ remove: removeNodeHook,
4550
+ hydrate: (vNode, node) => {
4551
+ var _a; // @todo tests.
4552
+
4553
+
4554
+ if (process.env.NODE_ENV !== 'production') {
4555
+ // eslint-disable-next-line lwc-internal/no-global-node
4556
+ if (node.nodeType !== Node.COMMENT_NODE) {
4557
+ logError('Hydration mismatch: incorrect node type received', vNode.owner);
4558
+ assert.fail('Hydration mismatch: incorrect node type received.');
4559
+ }
4560
+ } // always set the text value to the one from the vnode.
4561
+
4562
+
4563
+ node.nodeValue = (_a = vNode.text) !== null && _a !== void 0 ? _a : null;
4564
+ vNode.elm = node;
4565
+ }
4355
4566
  }; // insert is called after update, which is used somewhere else (via a module)
4356
4567
  // to mark the vm as inserted, that means we cannot use update as the main channel
4357
4568
  // to rehydrate when dirty, because sometimes the element is not inserted just yet,
@@ -4391,6 +4602,12 @@ const ElementHook = {
4391
4602
  remove: (vnode, parentNode) => {
4392
4603
  removeNodeHook(vnode, parentNode);
4393
4604
  removeElmHook(vnode);
4605
+ },
4606
+ hydrate: (vnode, node) => {
4607
+ vnode.elm = node;
4608
+ hydrateElmHook(vnode); // hydrate children hook
4609
+
4610
+ hydrateChildrenHook(vnode.elm.childNodes, vnode.children, vnode.owner);
4394
4611
  }
4395
4612
  };
4396
4613
  const CustomElementHook = {
@@ -4482,6 +4699,52 @@ const CustomElementHook = {
4482
4699
  // will take care of disconnecting any child VM attached to its shadow as well.
4483
4700
  removeVM(vm);
4484
4701
  }
4702
+ },
4703
+ hydrate: (vnode, elm) => {
4704
+ // the element is created, but the vm is not
4705
+ const {
4706
+ sel,
4707
+ mode,
4708
+ ctor,
4709
+ owner
4710
+ } = vnode;
4711
+ const def = getComponentInternalDef(ctor);
4712
+ createVM(elm, def, {
4713
+ mode,
4714
+ owner,
4715
+ tagName: sel,
4716
+ renderer: owner.renderer
4717
+ });
4718
+ vnode.elm = elm;
4719
+ const vm = getAssociatedVMIfPresent(elm);
4720
+
4721
+ if (vm) {
4722
+ allocateChildrenHook(vnode, vm);
4723
+ }
4724
+
4725
+ hydrateElmHook(vnode); // Insert hook section:
4726
+
4727
+ if (vm) {
4728
+ if (process.env.NODE_ENV !== 'production') {
4729
+ assert.isTrue(vm.state === 0
4730
+ /* created */
4731
+ , `${vm} cannot be recycled.`);
4732
+ }
4733
+
4734
+ runConnectedCallback(vm);
4735
+ }
4736
+
4737
+ if (!(vm && vm.renderMode === 0
4738
+ /* Light */
4739
+ )) {
4740
+ // VM is not rendering in Light DOM, we can proceed and hydrate the slotted content.
4741
+ // Note: for Light DOM, this is handled while hydrating the VM
4742
+ hydrateChildrenHook(vnode.elm.childNodes, vnode.children, vm);
4743
+ }
4744
+
4745
+ if (vm) {
4746
+ hydrateVM(vm);
4747
+ }
4485
4748
  }
4486
4749
  };
4487
4750
 
@@ -5132,7 +5395,7 @@ function createStylesheet(vm, stylesheets) {
5132
5395
  for (let i = 0; i < stylesheets.length; i++) {
5133
5396
  renderer.insertGlobalStylesheet(stylesheets[i]);
5134
5397
  }
5135
- } else if (renderer.ssr) {
5398
+ } else if (renderer.ssr || renderer.isHydrating) {
5136
5399
  // native shadow or light DOM, SSR
5137
5400
  const combinedStylesheetContent = ArrayJoin.call(stylesheets, '\n');
5138
5401
  return createInlineStyleVNode(combinedStylesheetContent);
@@ -5318,9 +5581,9 @@ function validateLightDomTemplate(template, vm) {
5318
5581
  if (vm.renderMode === 0
5319
5582
  /* Light */
5320
5583
  ) {
5321
- assert.isTrue(template.renderMode === 'light', `Light DOM components can't render shadow DOM templates. Add an 'lwc:render-mode="light"' directive on the root template tag.`);
5584
+ assert.isTrue(template.renderMode === 'light', `Light DOM components can't render shadow DOM templates. Add an 'lwc:render-mode="light"' directive to the root template tag of ${getComponentTag(vm)}.`);
5322
5585
  } else {
5323
- assert.isTrue(isUndefined$1(template.renderMode), `Shadow DOM components template can't render light DOM templates. Either remove the 'lwc:render-mode' directive or set it to 'lwc:render-mode="shadow"`);
5586
+ assert.isTrue(isUndefined$1(template.renderMode), `Shadow DOM components template can't render light DOM templates. Either remove the 'lwc:render-mode' directive from ${getComponentTag(vm)} or set it to 'lwc:render-mode="shadow"`);
5324
5587
  }
5325
5588
  }
5326
5589
 
@@ -5729,12 +5992,28 @@ function connectRootElement(elm) {
5729
5992
  /* GlobalHydrate */
5730
5993
  , vm);
5731
5994
  }
5995
+ function hydrateRootElement(elm) {
5996
+ const vm = getAssociatedVM(elm); // Usually means moving the element from one place to another, which is observable via
5997
+ // life-cycle hooks.
5998
+
5999
+ if (vm.state === 1
6000
+ /* connected */
6001
+ ) {
6002
+ disconnectRootElement(elm);
6003
+ }
6004
+
6005
+ runConnectedCallback(vm);
6006
+ hydrateVM(vm); // should we hydrate the root children? light dom case.
6007
+ }
5732
6008
  function disconnectRootElement(elm) {
5733
6009
  const vm = getAssociatedVM(elm);
5734
6010
  resetComponentStateWhenRemoved(vm);
5735
6011
  }
5736
6012
  function appendVM(vm) {
5737
6013
  rehydrate(vm);
6014
+ }
6015
+ function hydrateVM(vm) {
6016
+ hydrate(vm);
5738
6017
  } // just in case the component comes back, with this we guarantee re-rendering it
5739
6018
  // while preventing any attempt to rehydration until after reinsertion.
5740
6019
 
@@ -5959,6 +6238,22 @@ function rehydrate(vm) {
5959
6238
  }
5960
6239
  }
5961
6240
 
6241
+ function hydrate(vm) {
6242
+ if (isTrue(vm.isDirty)) {
6243
+ // manually diffing/patching here.
6244
+ // This routine is:
6245
+ // patchShadowRoot(vm, children);
6246
+ // -> addVnodes.
6247
+ const children = renderComponent(vm);
6248
+ vm.children = children;
6249
+ const vmChildren = vm.renderMode === 0
6250
+ /* Light */
6251
+ ? vm.elm.childNodes : vm.elm.shadowRoot.childNodes;
6252
+ hydrateChildrenHook(vmChildren, children, vm);
6253
+ runRenderedCallback(vm);
6254
+ }
6255
+ }
6256
+
5962
6257
  function patchShadowRoot(vm, newCh) {
5963
6258
  const {
5964
6259
  children: oldCh
@@ -6762,5 +7057,5 @@ function readonly(obj) {
6762
7057
  return reactiveMembrane.getReadOnlyProxy(obj);
6763
7058
  }
6764
7059
 
6765
- export { LightningElement, profilerControl as __unstable__ProfilerControl, api$1 as api, connectRootElement, createContextProvider, createVM, disconnectRootElement, getAssociatedVMIfPresent, getComponentDef, getComponentInternalDef, getUpgradableConstructor, isComponentConstructor, readonly, register, registerComponent, registerDecorators, registerTemplate, sanitizeAttribute, swapComponent, swapStyle, swapTemplate, track, unwrap, wire };
6766
- /* version: 2.5.1 */
7060
+ export { LightningElement, profilerControl as __unstable__ProfilerControl, api$1 as api, connectRootElement, createContextProvider, createVM, disconnectRootElement, getAssociatedVMIfPresent, getComponentDef, getComponentInternalDef, getUpgradableConstructor, hydrateRootElement, isComponentConstructor, readonly, register, registerComponent, registerDecorators, registerTemplate, sanitizeAttribute, swapComponent, swapStyle, swapTemplate, track, unwrap, wire };
7061
+ /* version: 2.5.2-canary1 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lwc/engine-core",
3
- "version": "2.5.1",
3
+ "version": "2.5.2-canary1",
4
4
  "description": "Core LWC engine APIs.",
5
5
  "homepage": "https://lwc.dev/",
6
6
  "repository": {
@@ -24,8 +24,8 @@
24
24
  "types/"
25
25
  ],
26
26
  "dependencies": {
27
- "@lwc/features": "2.5.1",
28
- "@lwc/shared": "2.5.1"
27
+ "@lwc/features": "2.5.2-canary1",
28
+ "@lwc/shared": "2.5.2-canary1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "observable-membrane": "1.0.1"
@@ -33,5 +33,5 @@
33
33
  "publishConfig": {
34
34
  "access": "public"
35
35
  },
36
- "gitHead": "443fa2e0a54c8940814292aac84d40e7eb69b22f"
36
+ "gitHead": "9704f323be88e79bbffb964e4805e6cbf18556ec"
37
37
  }
@@ -70,6 +70,7 @@ export interface Hooks<N extends VNode> {
70
70
  move: (vNode: N, parentNode: Node, referenceNode: Node | null) => void;
71
71
  update: (oldVNode: N, vNode: N) => void;
72
72
  remove: (vNode: N, parentNode: Node) => void;
73
+ hydrate: (vNode: N, node: Node) => void;
73
74
  }
74
75
  export interface Module<N extends VNode> {
75
76
  create?: (vNode: N) => void;
@@ -1,9 +1,11 @@
1
1
  import { VM } from './vm';
2
2
  import { VNode, VCustomElement, VElement, VNodes } from '../3rdparty/snabbdom/types';
3
+ export declare function hydrateNodeHook(vNode: VNode, node: Node): void;
3
4
  export declare function updateNodeHook(oldVnode: VNode, vnode: VNode): void;
4
5
  export declare function insertNodeHook(vnode: VNode, parentNode: Node, referenceNode: Node | null): void;
5
6
  export declare function removeNodeHook(vnode: VNode, parentNode: Node): void;
6
7
  export declare function createElmHook(vnode: VElement): void;
8
+ export declare function hydrateElmHook(vnode: VElement): void;
7
9
  export declare function fallbackElmHook(elm: Element, vnode: VElement): void;
8
10
  export declare function updateElmHook(oldVnode: VElement, vnode: VElement): void;
9
11
  export declare function updateChildrenHook(oldVnode: VElement, vnode: VElement): void;
@@ -11,6 +13,7 @@ export declare function allocateChildrenHook(vnode: VCustomElement, vm: VM): voi
11
13
  export declare function createViewModelHook(elm: HTMLElement, vnode: VCustomElement): void;
12
14
  export declare function createCustomElmHook(vnode: VCustomElement): void;
13
15
  export declare function createChildrenHook(vnode: VElement): void;
16
+ export declare function hydrateChildrenHook(elmChildren: NodeListOf<ChildNode>, children: VNodes, vm?: VM): void;
14
17
  export declare function updateCustomElmHook(oldVnode: VCustomElement, vnode: VCustomElement): void;
15
18
  export declare function removeElmHook(vnode: VElement): void;
16
19
  export declare function markAsDynamicChildren(children: VNodes): void;
@@ -7,7 +7,7 @@ export { default as wire } from './decorators/wire';
7
7
  export { readonly } from './readonly';
8
8
  export { setFeatureFlag, setFeatureFlagForTest } from '@lwc/features';
9
9
  export { getComponentInternalDef } from './def';
10
- export { createVM, connectRootElement, disconnectRootElement, getAssociatedVMIfPresent, } from './vm';
10
+ export { createVM, connectRootElement, disconnectRootElement, getAssociatedVMIfPresent, hydrateRootElement, } from './vm';
11
11
  export { registerComponent } from './component';
12
12
  export { registerTemplate } from './secure-template';
13
13
  export { registerDecorators } from './decorators/register';
@@ -2,6 +2,7 @@ export declare type HostNode = any;
2
2
  export declare type HostElement = any;
3
3
  export interface Renderer<N = HostNode, E = HostElement> {
4
4
  ssr: boolean;
5
+ readonly isHydrating: boolean;
5
6
  isNativeShadowDefined: boolean;
6
7
  isSyntheticShadowDefined: boolean;
7
8
  insert(node: N, parent: E, anchor: N | null): void;
@@ -118,8 +118,10 @@ export interface VM<N = HostNode, E = HostElement> {
118
118
  declare type VMAssociable = HostNode | LightningElement;
119
119
  export declare function rerenderVM(vm: VM): void;
120
120
  export declare function connectRootElement(elm: any): void;
121
+ export declare function hydrateRootElement(elm: any): void;
121
122
  export declare function disconnectRootElement(elm: any): void;
122
123
  export declare function appendVM(vm: VM): void;
124
+ export declare function hydrateVM(vm: VM): void;
123
125
  export declare function removeVM(vm: VM): void;
124
126
  export declare function createVM<HostNode, HostElement>(elm: HostElement, def: ComponentDef, options: {
125
127
  mode: ShadowRootMode;