@lwc/ssr-compiler 8.12.6 → 8.12.7

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.
@@ -20,8 +20,8 @@ export interface ComponentMetaState {
20
20
  tmplExplicitImports: Map<string, string> | null;
21
21
  cssExplicitImports: Map<string, string> | null;
22
22
  staticStylesheetIds: Set<string> | null;
23
- publicFields: Array<string>;
24
- privateFields: Array<string>;
23
+ publicProperties: Array<string>;
24
+ privateProperties: Array<string>;
25
25
  wireAdapters: WireAdapter[];
26
26
  experimentalDynamicComponent: ComponentTransformOptions['experimentalDynamicComponent'];
27
27
  importManager: ImportManager;
@@ -4,7 +4,7 @@ import type { TemplateOpts, TransformerContext } from './types';
4
4
  export declare function irChildrenToEs(children: IrChildNode[], cxt: TransformerContext, cb?: (child: IrChildNode) => (() => void) | void): EsStatement[];
5
5
  export declare function irToEs<T extends IrNode>(node: T, cxt: TransformerContext): EsStatement[];
6
6
  export declare function templateIrToEsTree(node: IrNode, contextOpts: TemplateOpts): {
7
- addImport: (imports: string | string[] | Record<string, string | undefined>, source?: string | undefined) => void;
7
+ addImport: (imports: string | string[] | Record<string, string | undefined>, source?: string) => void;
8
8
  getImports: () => import("estree").ImportDeclaration[];
9
9
  statements: EsStatement[];
10
10
  };
package/dist/index.cjs.js CHANGED
@@ -738,8 +738,8 @@ function bWireAdaptersPlumbing(adapters) {
738
738
  */
739
739
  const bGenerateMarkup = (esTemplate `
740
740
  // These variables may mix with component-authored variables, so should be reasonably unique
741
- const __lwcPublicFields__ = new Set(${ /*public fields*/estreeToolkit.is.arrayExpression});
742
- const __lwcPrivateFields__ = new Set(${ /*private fields*/estreeToolkit.is.arrayExpression});
741
+ const __lwcPublicProperties__ = new Set(${ /*api*/estreeToolkit.is.arrayExpression});
742
+ const __lwcPrivateProperties__ = new Set(${ /*private fields*/estreeToolkit.is.arrayExpression});
743
743
 
744
744
  async function* generateMarkup(
745
745
  tagName,
@@ -765,8 +765,8 @@ const bGenerateMarkup = (esTemplate `
765
765
  instance[__SYMBOL__SET_INTERNALS](
766
766
  props,
767
767
  attrs,
768
- __lwcPublicFields__,
769
- __lwcPrivateFields__,
768
+ __lwcPublicProperties__,
769
+ __lwcPrivateProperties__,
770
770
  );
771
771
  instance.isConnected = true;
772
772
  if (instance.connectedCallback) {
@@ -816,7 +816,7 @@ const bExposeTemplate = (esTemplate `
816
816
  * - deferring to the template function for yielding child content
817
817
  */
818
818
  function addGenerateMarkupFunction(program, state, tagName, filename) {
819
- const { privateFields, publicFields, tmplExplicitImports } = state;
819
+ const { privateProperties, publicProperties, tmplExplicitImports } = state;
820
820
  // The default tag name represents the component name that's passed to the transformer.
821
821
  // This is needed to generate markup for dynamic components which are invoked through
822
822
  // the generateMarkup function on the constructor.
@@ -846,7 +846,7 @@ function addGenerateMarkupFunction(program, state, tagName, filename) {
846
846
  SYMBOL__SET_INTERNALS: '__SYMBOL__SET_INTERNALS',
847
847
  establishContextfulRelationship: '__establishContextfulRelationship',
848
848
  }));
849
- program.body.push(...bGenerateMarkup(estreeToolkit.builders.arrayExpression(publicFields.map(estreeToolkit.builders.literal)), estreeToolkit.builders.arrayExpression(privateFields.map(estreeToolkit.builders.literal)), defaultTagName, classIdentifier, connectWireAdapterCode));
849
+ program.body.push(...bGenerateMarkup(estreeToolkit.builders.arrayExpression(publicProperties.map(estreeToolkit.builders.literal)), estreeToolkit.builders.arrayExpression(privateProperties.map(estreeToolkit.builders.literal)), defaultTagName, classIdentifier, connectWireAdapterCode));
850
850
  if (exposeTemplateBlock) {
851
851
  program.body.push(exposeTemplateBlock);
852
852
  }
@@ -939,23 +939,31 @@ const visitors = {
939
939
  },
940
940
  PropertyDefinition(path, state) {
941
941
  const node = path.node;
942
- if (!estreeToolkit.is.identifier(node?.key)) {
942
+ if (!node?.key) {
943
+ // Seems to occur for `@wire() [symbol];` -- not sure why
944
+ throw new Error('Unknown state: property definition has no key');
945
+ }
946
+ if (!estreeToolkit.is.identifier(node.key)) {
943
947
  return;
944
948
  }
945
949
  const { decorators } = node;
946
950
  validateUniqueDecorator(decorators);
947
951
  const decoratedExpression = decorators?.[0]?.expression;
948
952
  if (estreeToolkit.is.identifier(decoratedExpression) && decoratedExpression.name === 'api') {
949
- state.publicFields.push(node.key.name);
953
+ state.publicProperties.push(node.key.name);
950
954
  }
951
955
  else if (estreeToolkit.is.callExpression(decoratedExpression) &&
952
956
  estreeToolkit.is.identifier(decoratedExpression.callee) &&
953
957
  decoratedExpression.callee.name === 'wire') {
958
+ if (node.computed) {
959
+ // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler`
960
+ throw new Error('@wire cannot be used on computed properties in SSR context.');
961
+ }
954
962
  catalogWireAdapters(path, state);
955
- state.privateFields.push(node.key.name);
963
+ state.privateProperties.push(node.key.name);
956
964
  }
957
965
  else {
958
- state.privateFields.push(node.key.name);
966
+ state.privateProperties.push(node.key.name);
959
967
  }
960
968
  if (node.static &&
961
969
  node.key.name === 'stylesheets' &&
@@ -981,9 +989,15 @@ const visitors = {
981
989
  if (estreeToolkit.is.callExpression(decoratedExpression) &&
982
990
  estreeToolkit.is.identifier(decoratedExpression.callee) &&
983
991
  decoratedExpression.callee.name === 'wire') {
992
+ // not a getter/setter
993
+ const isRealMethod = node.kind === 'method';
994
+ if (node.computed) {
995
+ // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler`
996
+ throw new Error(`@wire cannot be used on computed ${isRealMethod ? 'method' : 'properties'} in SSR context.`);
997
+ }
984
998
  // Getters and setters are methods in the AST, but treated as properties by @wire
985
999
  // Note that this means that their implementations are ignored!
986
- if (node.kind === 'get' || node.kind === 'set') {
1000
+ if (!isRealMethod) {
987
1001
  const methodAsProp = estreeToolkit.builders.propertyDefinition(structuredClone(node.key), null, node.computed, node.static);
988
1002
  methodAsProp.decorators = structuredClone(decorators);
989
1003
  path.replaceWith(methodAsProp);
@@ -996,6 +1010,13 @@ const visitors = {
996
1010
  catalogWireAdapters(path, state);
997
1011
  }
998
1012
  }
1013
+ else if (estreeToolkit.is.identifier(decoratedExpression) && decoratedExpression.name === 'api') {
1014
+ if (state.publicProperties.includes(node.key.name)) {
1015
+ // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler`
1016
+ throw new Error(`LWC1112: @api get ${node.key.name} and @api set ${node.key.name} detected in class declaration. Only one of the two needs to be decorated with @api.`);
1017
+ }
1018
+ state.publicProperties.push(node.key.name);
1019
+ }
999
1020
  switch (node.key.name) {
1000
1021
  case 'constructor':
1001
1022
  // add our own custom arg after any pre-existing constructor args
@@ -1080,8 +1101,8 @@ function compileJS(src, filename, tagName, options, compilationMode) {
1080
1101
  tmplExplicitImports: null,
1081
1102
  cssExplicitImports: null,
1082
1103
  staticStylesheetIds: null,
1083
- publicFields: [],
1084
- privateFields: [],
1104
+ publicProperties: [],
1105
+ privateProperties: [],
1085
1106
  wireAdapters: [],
1086
1107
  experimentalDynamicComponent: options.experimentalDynamicComponent,
1087
1108
  importManager: new ImportManager(),
@@ -1476,7 +1497,7 @@ const bGenerateSlottedContent = (esTemplateWithYield `
1476
1497
  // than a function _declaration_, so it isn't available to be referenced anywhere.
1477
1498
  const bAddSlottedContent = (esTemplate `
1478
1499
  addSlottedContent(${ /* slot name */estreeToolkit.is.expression} ?? "", async function* generateSlottedContent(contextfulParent, ${
1479
- /* scoped slot data variable */ isNullableOf(estreeToolkit.is.identifier)}) {
1500
+ /* scoped slot data variable */ isNullableOf(estreeToolkit.is.identifier)}, slotAttributeValue) {
1480
1501
  // FIXME: make validation work again
1481
1502
  ${ /* slot content */false}
1482
1503
  }, ${ /* content map */estreeToolkit.is.identifier});
@@ -1624,6 +1645,13 @@ const bYieldFromChildGenerator = (esTemplateWithYield `
1624
1645
  {
1625
1646
  const childProps = ${ /* child props */estreeToolkit.is.objectExpression};
1626
1647
  const childAttrs = ${ /* child attrs */estreeToolkit.is.objectExpression};
1648
+ /*
1649
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1650
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1651
+ */
1652
+ if (slotAttributeValue) {
1653
+ childAttrs.slot = slotAttributeValue;
1654
+ }
1627
1655
  ${
1628
1656
  /*
1629
1657
  Slotted content is inserted here.
@@ -1677,6 +1705,13 @@ const bYieldFromDynamicComponentConstructorGenerator = (esTemplateWithYield `
1677
1705
  }
1678
1706
  const childProps = ${ /* child props */estreeToolkit.is.objectExpression};
1679
1707
  const childAttrs = ${ /* child attrs */estreeToolkit.is.objectExpression};
1708
+ /*
1709
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1710
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1711
+ */
1712
+ if (slotAttributeValue) {
1713
+ childAttrs.slot = slotAttributeValue;
1714
+ }
1680
1715
  ${
1681
1716
  /*
1682
1717
  Slotted content is inserted here.
@@ -1802,6 +1837,15 @@ const bConditionallyYieldScopeTokenClass = (esTemplateWithYield `
1802
1837
  yield \` class="\${stylesheetScopeToken}"\`;
1803
1838
  }
1804
1839
  `);
1840
+ /*
1841
+ If `slotAttributeValue` is set, it references a slot that does not exist, and the `slot` attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1842
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1843
+ */
1844
+ const bConditionallyYieldDanglingSlotName = (esTemplateWithYield `
1845
+ if (slotAttributeValue) {
1846
+ yield \` slot="\${slotAttributeValue}"\`;
1847
+ }
1848
+ `);
1805
1849
  const bYieldSanitizedHtml = esTemplateWithYield `
1806
1850
  yield sanitizeHtmlContent(${ /* lwc:inner-html content */estreeToolkit.is.expression})
1807
1851
  `;
@@ -1916,6 +1960,7 @@ const Element = function Element(node, cxt) {
1916
1960
  const isSelfClosingElement = shared.isVoidElement(node.name, shared.HTML_NAMESPACE) || isForeignSelfClosingElement;
1917
1961
  return [
1918
1962
  bYield(estreeToolkit.builders.literal(`<${node.name}`)),
1963
+ bConditionallyYieldDanglingSlotName(),
1919
1964
  // If we haven't already prefixed the scope token to an existing class, add an explicit class here
1920
1965
  ...(hasClassAttribute ? [] : [bConditionallyYieldScopeTokenClass()]),
1921
1966
  ...yieldAttrsAndProps,
@@ -2015,7 +2060,13 @@ const bConditionalSlot = (esTemplateWithYield `
2015
2060
  const scopedGenerators = scopedSlottedContent?.[slotName ?? ""];
2016
2061
  const mismatchedSlots = isScopedSlot ? lightGenerators : scopedGenerators;
2017
2062
  const generators = isScopedSlot ? scopedGenerators : lightGenerators;
2018
-
2063
+ /*
2064
+ If a slotAttributeValue is present, it should be provided for assignment to any slotted content. This behavior aligns with v1 and engine-dom.
2065
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example.
2066
+ Note the slot mapping does not work for scoped slots, so the slot name is not rendered in this case.
2067
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/scoped-slots for example.
2068
+ */
2069
+ const danglingSlotName = !isScopedSlot ? ${ /* slotAttributeValue */estreeToolkit.is.expression} || slotAttributeValue : null;
2019
2070
  // start bookend HTML comment for light DOM slot vfragment
2020
2071
  if (!isSlotted) {
2021
2072
  yield '<!---->';
@@ -2028,7 +2079,7 @@ const bConditionalSlot = (esTemplateWithYield `
2028
2079
 
2029
2080
  if (generators) {
2030
2081
  for (let i = 0; i < generators.length; i++) {
2031
- yield* generators[i](contextfulParent, ${ /* scoped slot data */isNullableOf(estreeToolkit.is.expression)});
2082
+ yield* generators[i](contextfulParent, ${ /* scoped slot data */isNullableOf(estreeToolkit.is.expression)}, danglingSlotName);
2032
2083
  // Scoped slotted data is separated by bookends. Final bookends are added outside of the loop below.
2033
2084
  if (isScopedSlot && i < generators.length - 1) {
2034
2085
  yield '<!---->';
@@ -2074,7 +2125,10 @@ const Slot = function Slot(node, ctx) {
2074
2125
  const slotChildren = irChildrenToEs(node.children, ctx);
2075
2126
  const isScopedSlot = estreeToolkit.builders.literal(Boolean(slotBound));
2076
2127
  const isSlotted = estreeToolkit.builders.literal(Boolean(ctx.isSlotted));
2077
- return [bConditionalSlot(isScopedSlot, isSlotted, slotName, slotBound, slotChildren, slotAst)];
2128
+ const slotAttributeValue = bAttributeValue(node, 'slot');
2129
+ return [
2130
+ bConditionalSlot(isScopedSlot, isSlotted, slotName, slotAttributeValue, slotBound, slotChildren, slotAst),
2131
+ ];
2078
2132
  };
2079
2133
 
2080
2134
  /*
@@ -2260,6 +2314,9 @@ const bExportTemplate = (esTemplate `
2260
2314
  let textContentBuffer = '';
2261
2315
  let didBufferTextContent = false;
2262
2316
 
2317
+ // This will get overridden but requires initialization.
2318
+ const slotAttributeValue = null;
2319
+
2263
2320
  // Establishes a contextual relationship between two components for ContextProviders.
2264
2321
  // This variable will typically get overridden (shadowed) within slotted content.
2265
2322
  const contextfulParent = instance;
@@ -2365,5 +2422,5 @@ function compileTemplateForSSR(src, filename, options, mode = shared.DEFAULT_SSR
2365
2422
 
2366
2423
  exports.compileComponentForSSR = compileComponentForSSR;
2367
2424
  exports.compileTemplateForSSR = compileTemplateForSSR;
2368
- /** version: 8.12.6 */
2425
+ /** version: 8.12.7 */
2369
2426
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -734,8 +734,8 @@ function bWireAdaptersPlumbing(adapters) {
734
734
  */
735
735
  const bGenerateMarkup = (esTemplate `
736
736
  // These variables may mix with component-authored variables, so should be reasonably unique
737
- const __lwcPublicFields__ = new Set(${ /*public fields*/is.arrayExpression});
738
- const __lwcPrivateFields__ = new Set(${ /*private fields*/is.arrayExpression});
737
+ const __lwcPublicProperties__ = new Set(${ /*api*/is.arrayExpression});
738
+ const __lwcPrivateProperties__ = new Set(${ /*private fields*/is.arrayExpression});
739
739
 
740
740
  async function* generateMarkup(
741
741
  tagName,
@@ -761,8 +761,8 @@ const bGenerateMarkup = (esTemplate `
761
761
  instance[__SYMBOL__SET_INTERNALS](
762
762
  props,
763
763
  attrs,
764
- __lwcPublicFields__,
765
- __lwcPrivateFields__,
764
+ __lwcPublicProperties__,
765
+ __lwcPrivateProperties__,
766
766
  );
767
767
  instance.isConnected = true;
768
768
  if (instance.connectedCallback) {
@@ -812,7 +812,7 @@ const bExposeTemplate = (esTemplate `
812
812
  * - deferring to the template function for yielding child content
813
813
  */
814
814
  function addGenerateMarkupFunction(program, state, tagName, filename) {
815
- const { privateFields, publicFields, tmplExplicitImports } = state;
815
+ const { privateProperties, publicProperties, tmplExplicitImports } = state;
816
816
  // The default tag name represents the component name that's passed to the transformer.
817
817
  // This is needed to generate markup for dynamic components which are invoked through
818
818
  // the generateMarkup function on the constructor.
@@ -842,7 +842,7 @@ function addGenerateMarkupFunction(program, state, tagName, filename) {
842
842
  SYMBOL__SET_INTERNALS: '__SYMBOL__SET_INTERNALS',
843
843
  establishContextfulRelationship: '__establishContextfulRelationship',
844
844
  }));
845
- program.body.push(...bGenerateMarkup(builders.arrayExpression(publicFields.map(builders.literal)), builders.arrayExpression(privateFields.map(builders.literal)), defaultTagName, classIdentifier, connectWireAdapterCode));
845
+ program.body.push(...bGenerateMarkup(builders.arrayExpression(publicProperties.map(builders.literal)), builders.arrayExpression(privateProperties.map(builders.literal)), defaultTagName, classIdentifier, connectWireAdapterCode));
846
846
  if (exposeTemplateBlock) {
847
847
  program.body.push(exposeTemplateBlock);
848
848
  }
@@ -935,23 +935,31 @@ const visitors = {
935
935
  },
936
936
  PropertyDefinition(path, state) {
937
937
  const node = path.node;
938
- if (!is.identifier(node?.key)) {
938
+ if (!node?.key) {
939
+ // Seems to occur for `@wire() [symbol];` -- not sure why
940
+ throw new Error('Unknown state: property definition has no key');
941
+ }
942
+ if (!is.identifier(node.key)) {
939
943
  return;
940
944
  }
941
945
  const { decorators } = node;
942
946
  validateUniqueDecorator(decorators);
943
947
  const decoratedExpression = decorators?.[0]?.expression;
944
948
  if (is.identifier(decoratedExpression) && decoratedExpression.name === 'api') {
945
- state.publicFields.push(node.key.name);
949
+ state.publicProperties.push(node.key.name);
946
950
  }
947
951
  else if (is.callExpression(decoratedExpression) &&
948
952
  is.identifier(decoratedExpression.callee) &&
949
953
  decoratedExpression.callee.name === 'wire') {
954
+ if (node.computed) {
955
+ // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler`
956
+ throw new Error('@wire cannot be used on computed properties in SSR context.');
957
+ }
950
958
  catalogWireAdapters(path, state);
951
- state.privateFields.push(node.key.name);
959
+ state.privateProperties.push(node.key.name);
952
960
  }
953
961
  else {
954
- state.privateFields.push(node.key.name);
962
+ state.privateProperties.push(node.key.name);
955
963
  }
956
964
  if (node.static &&
957
965
  node.key.name === 'stylesheets' &&
@@ -977,9 +985,15 @@ const visitors = {
977
985
  if (is.callExpression(decoratedExpression) &&
978
986
  is.identifier(decoratedExpression.callee) &&
979
987
  decoratedExpression.callee.name === 'wire') {
988
+ // not a getter/setter
989
+ const isRealMethod = node.kind === 'method';
990
+ if (node.computed) {
991
+ // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler`
992
+ throw new Error(`@wire cannot be used on computed ${isRealMethod ? 'method' : 'properties'} in SSR context.`);
993
+ }
980
994
  // Getters and setters are methods in the AST, but treated as properties by @wire
981
995
  // Note that this means that their implementations are ignored!
982
- if (node.kind === 'get' || node.kind === 'set') {
996
+ if (!isRealMethod) {
983
997
  const methodAsProp = builders.propertyDefinition(structuredClone(node.key), null, node.computed, node.static);
984
998
  methodAsProp.decorators = structuredClone(decorators);
985
999
  path.replaceWith(methodAsProp);
@@ -992,6 +1006,13 @@ const visitors = {
992
1006
  catalogWireAdapters(path, state);
993
1007
  }
994
1008
  }
1009
+ else if (is.identifier(decoratedExpression) && decoratedExpression.name === 'api') {
1010
+ if (state.publicProperties.includes(node.key.name)) {
1011
+ // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler`
1012
+ throw new Error(`LWC1112: @api get ${node.key.name} and @api set ${node.key.name} detected in class declaration. Only one of the two needs to be decorated with @api.`);
1013
+ }
1014
+ state.publicProperties.push(node.key.name);
1015
+ }
995
1016
  switch (node.key.name) {
996
1017
  case 'constructor':
997
1018
  // add our own custom arg after any pre-existing constructor args
@@ -1076,8 +1097,8 @@ function compileJS(src, filename, tagName, options, compilationMode) {
1076
1097
  tmplExplicitImports: null,
1077
1098
  cssExplicitImports: null,
1078
1099
  staticStylesheetIds: null,
1079
- publicFields: [],
1080
- privateFields: [],
1100
+ publicProperties: [],
1101
+ privateProperties: [],
1081
1102
  wireAdapters: [],
1082
1103
  experimentalDynamicComponent: options.experimentalDynamicComponent,
1083
1104
  importManager: new ImportManager(),
@@ -1472,7 +1493,7 @@ const bGenerateSlottedContent = (esTemplateWithYield `
1472
1493
  // than a function _declaration_, so it isn't available to be referenced anywhere.
1473
1494
  const bAddSlottedContent = (esTemplate `
1474
1495
  addSlottedContent(${ /* slot name */is.expression} ?? "", async function* generateSlottedContent(contextfulParent, ${
1475
- /* scoped slot data variable */ isNullableOf(is.identifier)}) {
1496
+ /* scoped slot data variable */ isNullableOf(is.identifier)}, slotAttributeValue) {
1476
1497
  // FIXME: make validation work again
1477
1498
  ${ /* slot content */false}
1478
1499
  }, ${ /* content map */is.identifier});
@@ -1620,6 +1641,13 @@ const bYieldFromChildGenerator = (esTemplateWithYield `
1620
1641
  {
1621
1642
  const childProps = ${ /* child props */is.objectExpression};
1622
1643
  const childAttrs = ${ /* child attrs */is.objectExpression};
1644
+ /*
1645
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1646
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1647
+ */
1648
+ if (slotAttributeValue) {
1649
+ childAttrs.slot = slotAttributeValue;
1650
+ }
1623
1651
  ${
1624
1652
  /*
1625
1653
  Slotted content is inserted here.
@@ -1673,6 +1701,13 @@ const bYieldFromDynamicComponentConstructorGenerator = (esTemplateWithYield `
1673
1701
  }
1674
1702
  const childProps = ${ /* child props */is.objectExpression};
1675
1703
  const childAttrs = ${ /* child attrs */is.objectExpression};
1704
+ /*
1705
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1706
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1707
+ */
1708
+ if (slotAttributeValue) {
1709
+ childAttrs.slot = slotAttributeValue;
1710
+ }
1676
1711
  ${
1677
1712
  /*
1678
1713
  Slotted content is inserted here.
@@ -1798,6 +1833,15 @@ const bConditionallyYieldScopeTokenClass = (esTemplateWithYield `
1798
1833
  yield \` class="\${stylesheetScopeToken}"\`;
1799
1834
  }
1800
1835
  `);
1836
+ /*
1837
+ If `slotAttributeValue` is set, it references a slot that does not exist, and the `slot` attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1838
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1839
+ */
1840
+ const bConditionallyYieldDanglingSlotName = (esTemplateWithYield `
1841
+ if (slotAttributeValue) {
1842
+ yield \` slot="\${slotAttributeValue}"\`;
1843
+ }
1844
+ `);
1801
1845
  const bYieldSanitizedHtml = esTemplateWithYield `
1802
1846
  yield sanitizeHtmlContent(${ /* lwc:inner-html content */is.expression})
1803
1847
  `;
@@ -1912,6 +1956,7 @@ const Element = function Element(node, cxt) {
1912
1956
  const isSelfClosingElement = isVoidElement(node.name, HTML_NAMESPACE) || isForeignSelfClosingElement;
1913
1957
  return [
1914
1958
  bYield(builders.literal(`<${node.name}`)),
1959
+ bConditionallyYieldDanglingSlotName(),
1915
1960
  // If we haven't already prefixed the scope token to an existing class, add an explicit class here
1916
1961
  ...(hasClassAttribute ? [] : [bConditionallyYieldScopeTokenClass()]),
1917
1962
  ...yieldAttrsAndProps,
@@ -2011,7 +2056,13 @@ const bConditionalSlot = (esTemplateWithYield `
2011
2056
  const scopedGenerators = scopedSlottedContent?.[slotName ?? ""];
2012
2057
  const mismatchedSlots = isScopedSlot ? lightGenerators : scopedGenerators;
2013
2058
  const generators = isScopedSlot ? scopedGenerators : lightGenerators;
2014
-
2059
+ /*
2060
+ If a slotAttributeValue is present, it should be provided for assignment to any slotted content. This behavior aligns with v1 and engine-dom.
2061
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example.
2062
+ Note the slot mapping does not work for scoped slots, so the slot name is not rendered in this case.
2063
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/scoped-slots for example.
2064
+ */
2065
+ const danglingSlotName = !isScopedSlot ? ${ /* slotAttributeValue */is.expression} || slotAttributeValue : null;
2015
2066
  // start bookend HTML comment for light DOM slot vfragment
2016
2067
  if (!isSlotted) {
2017
2068
  yield '<!---->';
@@ -2024,7 +2075,7 @@ const bConditionalSlot = (esTemplateWithYield `
2024
2075
 
2025
2076
  if (generators) {
2026
2077
  for (let i = 0; i < generators.length; i++) {
2027
- yield* generators[i](contextfulParent, ${ /* scoped slot data */isNullableOf(is.expression)});
2078
+ yield* generators[i](contextfulParent, ${ /* scoped slot data */isNullableOf(is.expression)}, danglingSlotName);
2028
2079
  // Scoped slotted data is separated by bookends. Final bookends are added outside of the loop below.
2029
2080
  if (isScopedSlot && i < generators.length - 1) {
2030
2081
  yield '<!---->';
@@ -2070,7 +2121,10 @@ const Slot = function Slot(node, ctx) {
2070
2121
  const slotChildren = irChildrenToEs(node.children, ctx);
2071
2122
  const isScopedSlot = builders.literal(Boolean(slotBound));
2072
2123
  const isSlotted = builders.literal(Boolean(ctx.isSlotted));
2073
- return [bConditionalSlot(isScopedSlot, isSlotted, slotName, slotBound, slotChildren, slotAst)];
2124
+ const slotAttributeValue = bAttributeValue(node, 'slot');
2125
+ return [
2126
+ bConditionalSlot(isScopedSlot, isSlotted, slotName, slotAttributeValue, slotBound, slotChildren, slotAst),
2127
+ ];
2074
2128
  };
2075
2129
 
2076
2130
  /*
@@ -2256,6 +2310,9 @@ const bExportTemplate = (esTemplate `
2256
2310
  let textContentBuffer = '';
2257
2311
  let didBufferTextContent = false;
2258
2312
 
2313
+ // This will get overridden but requires initialization.
2314
+ const slotAttributeValue = null;
2315
+
2259
2316
  // Establishes a contextual relationship between two components for ContextProviders.
2260
2317
  // This variable will typically get overridden (shadowed) within slotted content.
2261
2318
  const contextfulParent = instance;
@@ -2360,5 +2417,5 @@ function compileTemplateForSSR(src, filename, options, mode = DEFAULT_SSR_MODE)
2360
2417
  }
2361
2418
 
2362
2419
  export { compileComponentForSSR, compileTemplateForSSR };
2363
- /** version: 8.12.6 */
2420
+ /** version: 8.12.7 */
2364
2421
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/ssr-compiler",
7
- "version": "8.12.6",
7
+ "version": "8.12.7",
8
8
  "description": "Compile component for use during server-side rendering",
9
9
  "keywords": [
10
10
  "compiler",
@@ -48,10 +48,10 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@babel/types": "7.26.5",
52
- "@lwc/shared": "8.12.6",
53
- "@lwc/errors": "8.12.6",
54
- "@lwc/template-compiler": "8.12.6",
51
+ "@babel/types": "7.26.7",
52
+ "@lwc/shared": "8.12.7",
53
+ "@lwc/errors": "8.12.7",
54
+ "@lwc/template-compiler": "8.12.7",
55
55
  "acorn": "8.14.0",
56
56
  "astring": "^1.9.0",
57
57
  "estree-toolkit": "^1.7.8",
@@ -59,7 +59,7 @@
59
59
  "meriyah": "^5.0.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@lwc/babel-plugin-component": "8.12.6",
62
+ "@lwc/babel-plugin-component": "8.12.7",
63
63
  "@types/estree": "^1.0.6"
64
64
  }
65
65
  }